<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://blog.marco79423.net/</id>
    <title>大類的技術手記</title>
    <updated>2026-03-31T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <author>
        <name>兩大類</name>
        <email>marco79423@gmail.com</email>
        <uri>https://blog.marco79423.net</uri>
    </author>
    <link rel="alternate" href="https://blog.marco79423.net/"/>
    <link rel="self" href="https://blog.marco79423.net/api/atom.xml"/>
    <subtitle>兩大類的個人網站，主要都是在寫學習心得(Python, Javascript, 網站設計, 程式技能等)、作品和一些胡言亂語的東西</subtitle>
    <logo>https://blog.marco79423.net/img/logo.jpg</logo>
    <icon>https://blog.marco79423.net/favicon.ico</icon>
    <rights>Copyright © 2026 - 兩大類</rights>
    <entry>
        <title type="html"><![CDATA[隨手記 - 啪唧分帳]]></title>
        <id>https://blog.marco79423.net/articles/%E9%9A%A8%E6%89%8B%E8%A8%98-%E5%95%AA%E5%94%A7%E5%88%86%E5%B8%B3/</id>
        <link href="https://blog.marco79423.net/articles/%E9%9A%A8%E6%89%8B%E8%A8%98-%E5%95%AA%E5%94%A7%E5%88%86%E5%B8%B3/"/>
        <updated>2026-03-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[
網站： https://splitly.pajitool.com/
來分享一下我閉關三個月做的新作品－－啪唧分帳。
顧名思義，這就是一個分帳工具。不過如果只是「大家吃完飯後平分一下錢」那種程度，其實市面上早就有很多產品了，所以我這次想做的方向不是只有把金額除一除而已，而是希望它在多人協作 ...]]></summary>
        <content type="html"><![CDATA[&lt;img alt=&quot;1.png&quot; src=&quot;https://blog.marco79423.net/backend/static/26b5b27c-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 260px;&quot; /&gt;
&lt;p&gt;網站： &lt;a class=&quot;reference external&quot; href=&quot;https://splitly.pajitool.com/&quot;&gt;https://splitly.pajitool.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;來分享一下我閉關三個月做的新作品－－啪唧分帳。&lt;/p&gt;
&lt;p&gt;顧名思義，這就是一個分帳工具。不過如果只是「大家吃完飯後平分一下錢」那種程度，其實市面上早就有很多產品了，所以我這次想做的方向不是只有把金額除一除而已，而是希望它在多人協作、即時同步、結算體驗和手機操作上都能再往前推一點。&lt;/p&gt;
&lt;p&gt;花了大量的時間在使用 AI，目前的心得是目前 AI 離「一句話就能生出一個完整產品」這件事還有一段距離，做出能動的玩意兒確實很容易，但真要磨到能用、好用還是需要時間。&lt;/p&gt;
&lt;p&gt;何況深究其中產生的程式碼還是會覺得問題不少，雖說乍看生出的程式碼品質確實不錯（這邊尤其要稱讚一下 Claude），但只能看局部而已。&lt;/p&gt;
&lt;p&gt;如果往大的方面看就會發現不太行，好比說你要它修改畫作的某一處，它會把那一處修改得還不賴，但如果你站遠一點看整幅畫，就會覺得那邊怎麼看怎麼怪，結果還是得自己下去一個口令一個動作的修。&lt;/p&gt;
&lt;p&gt;但不管怎麼說 AI 確實幫助很大，這個規模的作品之前的我絕對是想都不敢想，但現在卻真的做出來了，這件事本身－－就很爽！&lt;/p&gt;
&lt;p&gt;科科。&lt;/p&gt;
&lt;p&gt;因為我 OpenAI 的 Codex、Anthropic 的 Claude 和 Google 的 Gemini 都有在用，而且都算是大量使用，所以剛好也能分享一下我小小的使用心得。&lt;/p&gt;
&lt;p&gt;首先自然是大家都在大推的 Claude Code，不用說，確實是目前我最滿意的，至少在寫程式這一塊我覺得是我用過最好的。&lt;/p&gt;
&lt;p&gt;另外還有一項可能沒多少人會在意的事情－－那就是我覺得和它語音對話的時候，那個英文的口說和 Gemini 和 ChatGPT 相比聽起來最自然（單指口說本身，不是指內容）……呃，這是題外話，不是很重要 XD。&lt;/p&gt;
&lt;p&gt;但 Claude Code 的問題是 token 量太少了，用幾次就沒了。所以必須使用 Max 方案才行，所以成本明顯比其他兩個貴，而且是貴非常非常多。&lt;/p&gt;
&lt;p&gt;至於 Gemini 的部分我個人最有意見，不是說完全不能用，但它卻很容易做出一些讓人血壓上升的事情。&lt;/p&gt;
&lt;p&gt;比如說我明明叫它－－「改完後先不要 commit，等我確認完沒問題再 commit。」&lt;/p&gt;
&lt;p&gt;它改完後還是可能會直接給你 commit 加上 push。質問它為何要這麼做，他會說：「對不起，你明明有要求不要 commit，但我還是 commit 加 push 了。」&lt;/p&gt;
&lt;p&gt;然後呢？然後它還會繼續這麼幹，然後繼續道歉，然後再繼續這麼幹……這種事情反覆發生過好幾次了，其他家都沒發生過這種情況，但 Gemini 就是很愛這樣做。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;聽說最近有更新解決這個問題，但我還沒試過效果，因為我現在主力變成 Claude 了。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;不過特別要說一下，如果是用線上的 Jules，量倒是真的很敢給，幾乎可以說是用不完，這點非常兇猛。所以我在散步發想的時候，很喜歡用它來亂做一些東西，而且通常效果也不差，反而程式碼本身之後再找 Claude 修就好。但它執行真的很慢、非常慢，要等很久，如果急用就不適合。&lt;/p&gt;
&lt;p&gt;Codex 的感覺大概就在中間，沒有讓我覺得像 Claude 那樣全面壓制，但整體表現通常比 Gemini 更穩定，量也給得不錯。所以如果 Claude 的額度用完了，我大多會優先切去用 Codex。順帶一提，如果我想要做「美美的東西」，通常 Codex 的表現還不錯。&lt;/p&gt;
&lt;p&gt;講完 AI，再講回產品本身。&lt;/p&gt;
&lt;p&gt;啪唧分帳的核心目標，是想把「多人一起花錢」這件事處理得更順。比方說聚餐、旅遊、同居、水電瓦斯、社團活動，這些場景其實都很煩，煩的點不是算不出來，而是過程很碎，還會一直變。&lt;/p&gt;
&lt;img alt=&quot;4.png&quot; src=&quot;https://blog.marco79423.net/backend/static/26b56312-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 80%;&quot; /&gt;
&lt;p&gt;有人先代墊、有人晚點才補記、有人只參與其中幾筆、有人是固定金額、有人是按比例分攤，而且大家通常還不是坐在同一台電腦前慢慢輸入，多半是各自拿手機邊走邊記。&lt;/p&gt;
&lt;img alt=&quot;2.png&quot; src=&quot;https://blog.marco79423.net/backend/static/26b5f8e0-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 35%;&quot; /&gt;
&lt;img alt=&quot;3.png&quot; src=&quot;https://blog.marco79423.net/backend/static/26b50430-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 35%;&quot; /&gt;
&lt;p&gt;啪唧分帳目前比較重要的功能大概有下面這些：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;15%&quot; /&gt;
&lt;col width=&quot;85%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;功能&lt;/td&gt;
&lt;td&gt;說明&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;免註開帳&lt;/td&gt;
&lt;td&gt;免註冊直接開始分帳。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;多人協作&lt;/td&gt;
&lt;td&gt;可以快速邀請其他人加入同一個帳本。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;即時同步&lt;/td&gt;
&lt;td&gt;多人同時編輯時會即時同步。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;派對功能&lt;/td&gt;
&lt;td&gt;可以抽籤決定誰付錢。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;分攤方式&lt;/td&gt;
&lt;td&gt;支援平均分、百分比、固定金額、權重份數。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;圖表結算&lt;/td&gt;
&lt;td&gt;會自動整理債務關係並畫成圖表。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;結算分享&lt;/td&gt;
&lt;td&gt;可以直接分享結算頁，讓大家看懂欠款關係。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;結清管理&lt;/td&gt;
&lt;td&gt;可以管理每個人各自結清的帳目。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;紀錄追蹤&lt;/td&gt;
&lt;td&gt;每筆帳目的建立、修改和刪除都有修改紀錄。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;離線使用&lt;/td&gt;
&lt;td&gt;網路不穩時也能先記，之後再補同步。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;其中我自己比較在意的是這個產品要給人很好的體驗，而不只是有那些功能，所以花了不少時間在調整細節。因為我覺得如果一個產品的功能表很長，但用起來很麻煩，那也不算是一個好產品。&lt;/p&gt;
&lt;p&gt;當然了，我一個人也不能保證能注意到所有問題，所以有發現問題的話也歡迎回報給我，讓我有機會修掉它。&lt;/p&gt;
&lt;p&gt;以上。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[隨手記 - K8s 的憑證問題解決]]></title>
        <id>https://blog.marco79423.net/articles/%E9%9A%A8%E6%89%8B%E8%A8%98-k8s-%E7%9A%84%E6%86%91%E8%AD%89%E5%95%8F%E9%A1%8C%E8%A7%A3%E6%B1%BA/</id>
        <link href="https://blog.marco79423.net/articles/%E9%9A%A8%E6%89%8B%E8%A8%98-k8s-%E7%9A%84%E6%86%91%E8%AD%89%E5%95%8F%E9%A1%8C%E8%A7%A3%E6%B1%BA/"/>
        <updated>2023-08-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近想幫自己的網站降低一些成本，我的主要網站是使用 GKE、Cloud SQL、Load Balancing 這幾個工具。
但這些組合有點太貴了，所以我打算減少使用 Load Balancing，不再使用 Google 的憑證 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;最近想幫自己的網站降低一些成本，我的主要網站是使用 GKE、Cloud SQL、Load Balancing 這幾個工具。&lt;/p&gt;
&lt;p&gt;但這些組合有點太貴了，所以我打算減少使用 Load Balancing，不再使用 Google 的憑證，而改用 Cert Manager 搭配 Let’s Encrypt 的憑證。&lt;/p&gt;
&lt;p&gt;其實我本來就有好幾個網站就有這樣使用了，所以原先以為不會出什麼差錯，應該一下就搞定的。&lt;/p&gt;
&lt;p&gt;沒想到卻卡了很久，明明 Cert Manager 有正確的拿到憑證，但實際去網站卻還是顯示 K8S 自簽的憑證。&lt;/p&gt;
&lt;p&gt;我找了很久都沒發現錯誤，無論是 ingress-nginx 還是 cert-manager 看起來都顯示正常，但就是無法使用。&lt;/p&gt;
&lt;p&gt;一直研究好久，才終於在 ingress-nginx 的 github 討論區看到有人提到。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
It turned out to be because I was only using defaultBackend in my Ingress, because I don&#39;t actually care about host or path-named routing; it seems to not like that. Adding a proper rules entry which listed the full hostname fixed the issue for me.
&lt;/pre&gt;
&lt;p&gt;(&lt;a class=&quot;reference external&quot; href=&quot;https://github.com/kubernetes/ingress-nginx/issues/4979#issuecomment-1635089483&quot;&gt;https://github.com/kubernetes/ingress-nginx/issues/4979#issuecomment-1635089483&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;疑？確實就是我的情況，前陣子在配置 GCP 的 Load Balancing 時我才知道 defaultBackend 的用法，所以全都改成這種方式了。&lt;/p&gt;
&lt;p&gt;（下面這種寫法）&lt;/p&gt;
&lt;pre class=&quot;code yaml literal-block&quot;&gt;
&lt;span class=&quot;nt&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;defaultBackend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l-Scalar-Plain&quot;&gt;wordpress&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l-Scalar-Plain&quot;&gt;80&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;於是我改成自己指定 rule。&lt;/p&gt;
&lt;p&gt;（下面這種寫法）&lt;/p&gt;
&lt;pre class=&quot;code yaml literal-block&quot;&gt;
&lt;span class=&quot;nt&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p-Indicator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l-Scalar-Plain&quot;&gt;xxxx.net&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p-Indicator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l-Scalar-Plain&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;pathType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l-Scalar-Plain&quot;&gt;Prefix&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l-Scalar-Plain&quot;&gt;wordpress&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l-Scalar-Plain&quot;&gt;80&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;成功！&lt;/p&gt;
&lt;p&gt;因為花了不少時間研究，所以留下記錄，給之後碰到同樣問題的人。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[隨手記 - 用 AI 寫 Bitfinex 放貸機器人]]></title>
        <id>https://blog.marco79423.net/articles/%E9%9A%A8%E6%89%8B%E8%A8%98-%E7%94%A8-ai-%E5%AF%AB-bitfinex-%E6%94%BE%E8%B2%B8%E6%A9%9F%E5%99%A8%E4%BA%BA/</id>
        <link href="https://blog.marco79423.net/articles/%E9%9A%A8%E6%89%8B%E8%A8%98-%E7%94%A8-ai-%E5%AF%AB-bitfinex-%E6%94%BE%E8%B2%B8%E6%A9%9F%E5%99%A8%E4%BA%BA/"/>
        <updated>2023-05-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近在嘗試 Bitfinex 放貸，想寫一個簡單的美金放貸機器人。
突然想說不如就用最近很夯的 AI 寫寫看？
想到就做，首先自然是帶動這波 AI 浪潮的 ChatGPT。
（要事先聲明的是我沒有付費，所以是 GPT-3 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;最近在嘗試 Bitfinex 放貸，想寫一個簡單的美金放貸機器人。&lt;/p&gt;
&lt;p&gt;突然想說不如就用最近很夯的 AI 寫寫看？&lt;/p&gt;
&lt;p&gt;想到就做，首先自然是帶動這波 AI 浪潮的 ChatGPT。&lt;/p&gt;
&lt;p&gt;（要事先聲明的是我沒有付費，所以是 GPT-3.5）&lt;/p&gt;
&lt;p&gt;以下是我的問題：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
實作一個 Bitfinex 自動放貸的 Python 程式，每一分鐘檢查錢包的餘額，如果大於等於 150 美金，就自動用全部的美金用 FRR 掛單，每 500 美金拆一單。
&lt;/pre&gt;
&lt;p&gt;而 ChatGPT 回我：&lt;/p&gt;
&lt;p&gt;（文字的部分）&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
在開始之前，請先確認您已經有 Bitfinex 的 API Key 和 Secret，並且已經開啟了 Lending 功能。

接下來，我們可以使用 Python 和 Bitfinex 的 API 來自動放貸。我們需要安裝 bitfinex 和 requests 這兩個模組，使用以下指令：

&amp;gt;&amp;gt;&amp;gt; pip install bitfinex requests
&lt;/pre&gt;
&lt;p&gt;（程式碼的部分）&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;bitfinex&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;requests&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;time&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 設定 API Key 和 Secret&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;YOUR_API_KEY&amp;#39;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;api_secret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;YOUR_API_SECRET&amp;#39;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 設定 FRR 掛單參數&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;currency&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;USD&amp;#39;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;frr&amp;#39;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 初始化 Bitfinex API 連線&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitfinex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClientV2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;api_secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 每一分鐘檢查錢包餘額&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;wallet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wallets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;balance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wallet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;balance&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;balance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 計算掛單數量&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;orders_num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;balance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 掛單&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;orders_num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;submit_offer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;（上面程式碼的註解不是我寫的，是 AI 自動生成的內容）&lt;/p&gt;
&lt;p&gt;結果看來確實有模有樣，甚至還有使用說明，包含需要先安裝哪些套件，相當厲害。&lt;/p&gt;
&lt;p&gt;不過這裡有個小問題，那就是它所使用的 &lt;a class=&quot;reference external&quot; href=&quot;https://pypi.org/project/bitfinex/&quot;&gt;bitfinex&lt;/a&gt; 並非官方的套件，而且其官網也找不到對應的 Python 程式碼，因此我不敢裝，不確定 AI 是否有正確使用該套件。&lt;/p&gt;
&lt;p&gt;但就算如此，稍微仔細一瞧也能看出實作的邏輯也有問題，比如說我的要求是「500 元拆一單」，但沒說不到 500 就不下訂單了，所以這樣的結果顯然有誤。&lt;/p&gt;
&lt;p&gt;再來是測試據說已經是 GPT-4 的 Bing AI。&lt;/p&gt;
&lt;p&gt;使用的方法很簡單，直接使用 Edge 瀏覽器即可，按一下 Edge 右上角 Bing 的 logo 就可以使用（不過要先排隊取得使用資格）。&lt;/p&gt;
&lt;img alt=&quot;1.jpg&quot; src=&quot;https://blog.marco79423.net/backend/static/221f1d2a-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;看起來雖然很方便，但實際使用時卻發現右側輸入框的大小竟然不能調整，所有內容都擠在一起，超小又超難讀，非常難用。&lt;/p&gt;
&lt;p&gt;這也就算了，我發現最嚴重的問題是其輸出的程式碼竟然沒有排版！？&lt;/p&gt;
&lt;p&gt;Python 的程式碼如果沒有排版，還能用嗎？&lt;/p&gt;
&lt;p&gt;相比起來，ChatGPT 的網站其程式碼會用令人舒服的方式呈現，體驗差距非常巨大。&lt;/p&gt;
&lt;img alt=&quot;1.jpg&quot; src=&quot;https://blog.marco79423.net/backend/static/221ecfb4-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;（其實在我寫作的當下已經 Edge 可以調整大小、而且程式碼也有排版了，後面會提到）&lt;/p&gt;
&lt;p&gt;總之，沒有辦法的我，突然想到只要再把這段沒排版的程式碼再拿回給 ChatGPT 排版不就得了？&lt;/p&gt;
&lt;p&gt;想到就做，所以我就複製貼上，希望 ChatGPT 幫我排版。&lt;/p&gt;
&lt;p&gt;結果被拒絕了……&lt;/p&gt;
&lt;img alt=&quot;3.jpg&quot; src=&quot;https://blog.marco79423.net/backend/static/221f5a60-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;於是我再求一次。&lt;/p&gt;
&lt;img alt=&quot;4.jpg&quot; src=&quot;https://blog.marco79423.net/backend/static/221f8efe-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;繼續求。&lt;/p&gt;
&lt;img alt=&quot;5.jpg&quot; src=&quot;https://blog.marco79423.net/backend/static/2220611c-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;改用哀兵戰術。&lt;/p&gt;
&lt;img alt=&quot;6.jpg&quot; src=&quot;https://blog.marco79423.net/backend/static/221fc3d8-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;改用考官模式。&lt;/p&gt;
&lt;img alt=&quot;7.jpg&quot; src=&quot;https://blog.marco79423.net/backend/static/221ffc22-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;於是 AI 接受挑戰。&lt;/p&gt;
&lt;p&gt;我發現 AI 似乎挺喜歡接受考驗的，之前只要用這個方法通常都可以成功。&lt;/p&gt;
&lt;p&gt;總之最後結果如下：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 匯入所需的套件&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;bitfinex_api_py.client.rest.wallets&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WalletClient&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;bitfinex_api_py.client.rest.funding&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FundingClient&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;schedule&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;time&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 設定 API 金鑰和密鑰&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;API_KEY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;your_api_key&amp;quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;API_SECRET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;your_api_secret&amp;quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 建立 WalletClient 和 FundingClient 物件&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;wallet_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WalletClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;API_KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;API_SECRET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;funding_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FundingClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;API_KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;API_SECRET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 定義檢查錢包餘額的函數&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;check_balance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 取得錢包的資訊&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;wallet_info&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wallet_client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_wallets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 遍歷錢包的資訊，找出美金的餘額&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wallet&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wallet_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wallet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;currency&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;USD&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;balance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wallet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;balance&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 如果美金餘額大於等於 150 美金，就呼叫 lend_all 函數&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;balance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lend_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;balance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 定義將美金全部用 FRR 掛單放貸的函數&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lend_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;balance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 計算需要拆分成幾單&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;num_orders&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;balance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 設定每單的金額為 500 美金或剩餘的金額（如果不足 500 美金）&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;amount_per_order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;balance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 遍歷每一單，發送放貸的請求&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num_orders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 設定放貸的參數，使用 FRR（Flash Return Rate）作為利率，期限為 30 天&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;LIMIT&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&amp;quot;symbol&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;fUSD&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&amp;quot;amount&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;amount_per_order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&amp;quot;rate&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&amp;quot;period&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;30&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&amp;quot;flags&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# FRR flag&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 發送放貸的請求，並印出回傳的結果&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;funding_client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;submit_offer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 設定每一分鐘執行一次 check_balance 函數&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;every&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minutes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;do&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;check_balance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 使用 while 迴圈持續執行&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 執行排程中的任務&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run_pending&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 暫停一秒&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;乍看起來也不錯，似乎比剛才的厲害，不過和前者一樣的問題，它同樣也沒用官方的函式庫，所以我也不知道用法對不對？我以為 Bing 的版本應該是可以查最新資料的，不知道為何仍有這樣的問題……&lt;/p&gt;
&lt;p&gt;而且同樣的，拆單的邏輯也同樣的錯誤，所以我覺得結果並不比 GPT-3.5 的 ChatGPT 更好。&lt;/p&gt;
&lt;p&gt;我個人的心得是目前的 AI 寫的程式快雖快，但還不太可靠，雖然相信未來發展非常有潛力，但至少目前還沒有。&lt;/p&gt;
&lt;p&gt;前面有約略提過，要寫 Bitfinex 的機器人，雖然可以打 API，但 Bitfinex 其實就有出官方的 &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/bitfinexcom/bitfinex-api-py&quot;&gt;bintfiex-api-py&lt;/a&gt; 套件可以使用，根本就不需要用其他民間自製的套件。&lt;/p&gt;
&lt;p&gt;簡單的用法如下：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;bfxapi&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bfxapi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;API_KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;API_SECRET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;API_SECRET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;列一下基本的操作：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 查看錢包&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;wallets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_wallets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 查看下的訂單（美金）&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;offers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_funding_offers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;fUSD&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 查看蠟燭圖&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;candles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_public_candles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;fUSD:p&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeframe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 下訂單&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;submit_funding_offer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;fUSD&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 美金&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;funding_type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FundingOffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LIMIT&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 用 FRR 下訂單&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;submit_funding_offer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;fUSD&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;funding_type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FundingOffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FRR_DELTA&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;另外要注意的是查詢訂單的時候會發現 FRR 訂單拿到的 rate 會是 0，這時可以用下面這個方法拿到當前的 FRR 利率。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frr_rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_public_ticker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;fUSD&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;完稿前，突然想要再一次嘗試一下 Edge 的 Bing AI，沒想到這次前面說的問題都解決了，既可以調整寬度，而且程式碼也有排版，雖然還是沒有語法高亮的功能，但至少可以直接使用了。&lt;/p&gt;
&lt;img alt=&quot;8.jpg&quot; src=&quot;https://blog.marco79423.net/backend/static/22202ea4-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;程式碼如下：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 實作一個 Bitfinex 自動放貸的 Python 程式&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 每一分鐘檢查錢包的餘額，如果大於等於 150 美金，就自動用全部的美金用 FRR 掛單，每 500 美金拆一單。&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 引入必要的套件&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;bfxapi&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 建立客戶端物件，填入 API 金鑰和密碼&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bfxapi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;&amp;lt;YOUR_API_KEY&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;API_SECRET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;&amp;lt;YOUR_API_SECRET&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 定義一個函數，用來放貸美金&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lend_usd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 設定放貸的參數，幣種為美金，利率為 FRR，期限為 30 天，可自動續約&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;symbol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;fUSD&amp;#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;FRR&amp;#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;period&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;renew&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# 如果放貸金額大於等於 500 美金，就拆成多筆放貸，每筆 500 美金&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_offer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;renew&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 如果還有剩餘的金額，就再放貸一筆&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_offer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;renew&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 回傳放貸的訂單列表&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 如果放貸金額小於 500 美金，就直接放貸一筆&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_offer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;renew&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 回傳放貸的訂單物件&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 定義一個無限迴圈，每一分鐘執行一次&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 取得錢包的資訊，並找出美金的餘額&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;wallet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wallet_balances&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;usd_balance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wallet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;exchange&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;USD&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;available&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# 如果美金的餘額大於等於 150 美金，就呼叫放貸函數，並印出結果&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;usd_balance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lend_usd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;usd_balance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;Lent &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;usd_balance&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt; USD with FRR.&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 如果美金的餘額小於 150 美金，就印出提示訊息&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;Not enough USD to lend. Balance: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;usd_balance&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt; USD.&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# 等待一分鐘後再執行迴圈&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;感覺明顯已經比之前好了，有用到官方套件，也有正確的處理不足 500 美金的部分，但也因為有正確使用官方套件，所以我這次也可以明確的說——這個套件不是這樣用的。&lt;/p&gt;
&lt;p&gt;AI 確實一如既往的在胡說八道，嗯嗯……&lt;/p&gt;
&lt;p&gt;＊＊＊&lt;/p&gt;
&lt;p&gt;最後的小結，我想說這只是個例子，我不是用這個策略放貸的。&lt;/p&gt;
&lt;p&gt;想問我有沒有好的投資策略？&lt;/p&gt;
&lt;p&gt;沒有。&lt;/p&gt;
&lt;p&gt;（其實我也很想知道，知道的可以私下留言給我 XD）&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[隨手記 - 新增 Argo CD 的通知]]></title>
        <id>https://blog.marco79423.net/articles/%E9%9A%A8%E6%89%8B%E8%A8%98-%E6%96%B0%E5%A2%9E-argo-cd-%E7%9A%84%E9%80%9A%E7%9F%A5/</id>
        <link href="https://blog.marco79423.net/articles/%E9%9A%A8%E6%89%8B%E8%A8%98-%E6%96%B0%E5%A2%9E-argo-cd-%E7%9A%84%E9%80%9A%E7%9F%A5/"/>
        <updated>2023-05-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[自從我將主要的傳訊軟體從 LINE 改為 Telegram 後，我就一直想要整合所有通知到 Telegram，因為 Telegram 不像 LINE 有次數上限，我可以盡情使用，不怕超過上限，能更方便得到最即時的通知 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;自從我將主要的傳訊軟體從 LINE 改為 Telegram 後，我就一直想要整合所有通知到 Telegram，因為 Telegram 不像 LINE 有次數上限，我可以盡情使用，不怕超過上限，能更方便得到最即時的通知。&lt;/p&gt;
&lt;p&gt;由於我的網站都是用 Argo CD 控管的，先前似乎有看到 Argo CD 也有相關的功能，可以直接用 Telegram 通知，所以決定這次就來嘗試看看。&lt;/p&gt;
&lt;p&gt;順帶一提，我在寫這篇文章的時候，發現一個有趣的問題——那就是我不確定到底是 ArgoCD 還是 Argo CD？&lt;/p&gt;
&lt;p&gt;我發現似乎兩者都有人用，但究竟哪一個才是對的呢？&lt;/p&gt;
&lt;p&gt;雖然最後我決定用當前官方網站上的名稱，也就是 Argo CD，但不知為何我一直有稱作 ArgoCD 的印象？&lt;/p&gt;
&lt;p&gt;不甘心的我為此還特地去查 &lt;a class=&quot;reference external&quot; href=&quot;https://archive.org/web/&quot;&gt;Wayback Machine&lt;/a&gt; 的記錄，發現從 Wayback Machine 有紀錄以來，官網確實一開始就叫 Argo CD……好吧，看來是我記錯了。&lt;/p&gt;
&lt;p&gt;不管如何，這次的重點是 Telegram 的通知，經過 Google 查詢，我發現有一個稱作 &lt;a class=&quot;reference external&quot; href=&quot;https://argocd-notifications.readthedocs.io/&quot;&gt;Argo CD Notifications&lt;/a&gt; 的專案可以做到這一點，於是便安裝來嘗試。&lt;/p&gt;
&lt;p&gt;但後來才發現其實這個專案早就在 &lt;a class=&quot;reference external&quot; href=&quot;https://argo-cd.readthedocs.io/en/stable/operator-manual/upgrading/2.2-2.3/#v22-to-23&quot;&gt;2.3&lt;/a&gt; 版時整合進 Argo CD 本體了，根本不需要另外裝什麼東西，原本就在運行，可以直接使用。&lt;/p&gt;
&lt;p&gt;通知的設定也很簡單，關鍵是兩個 Resource，分別是叫做 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;argocd-notifications-cm&lt;/span&gt;&lt;/tt&gt; 的 ConfigMap 和叫做 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;argocd-notifications-secret&lt;/span&gt;&lt;/tt&gt; 的 Secret。&lt;/p&gt;
&lt;p&gt;但其實所有的設定都是在 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;argocd-notifications-cm&lt;/span&gt;&lt;/tt&gt; 上，另一個只是單純紀錄所使用到的機密資訊，如帳密、Token 資訊而已，可用可不用。&lt;/p&gt;
&lt;p&gt;因此在嘗試階段，也可以直接設定在 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;argocd-notifications-cm&lt;/span&gt;&lt;/tt&gt; 就好，確定沒問題以後再使用 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;argocd-notifications-secret&lt;/span&gt;&lt;/tt&gt;。&lt;/p&gt;
&lt;p&gt;&lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;argocd-notifications-cm&lt;/span&gt;&lt;/tt&gt; 的設定主要可以分為幾個區塊：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;Triggers&lt;ul&gt;
&lt;li&gt;代表發生通知的條件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Notification Services&lt;ul&gt;
&lt;li&gt;負責通知的服務，像是最基本的 Email 和這次要用的 Telegram&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Subscriptions&lt;ul&gt;
&lt;li&gt;定義發送的條件和要發送的對象&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Template&lt;ul&gt;
&lt;li&gt;通知訊息的樣版&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;argocd-notifications-cm 的例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;v1&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;ConfigMap&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;argocd-notifications-cm&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# Trigger 發送通知的條件&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;trigger.on-sync-failed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;- when: app.status.operationState.phase in [&amp;#39;Error&amp;#39;, &amp;#39;Failed&amp;#39;]&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;description: Application syncing has failed&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;send:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;- app-sync-failed&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# Notification Services 通知服務，要用 Telegram Bot 就需設定 Token&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;service.telegram&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;token: $telegram-token&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# Subscriptions 發生的條件和對象&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;subscriptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;- recipients:  # 要傳送的位置，格式所示，服務:位置&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;- telegram:&amp;lt;chat_id&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;triggers:  # 傳送的條件&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;- on-sync-failed&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 訊息的樣版&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;template.app-sync-failed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;message: |&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;{{.app.metadata.name}} 出事啦: {{.app.status.operationState.message}}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;（此例的 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;telegram:&amp;lt;chat_id&amp;gt;&lt;/span&gt;&lt;/tt&gt; 的 &lt;tt class=&quot;docutils literal&quot;&gt;&amp;lt;chat_id&amp;gt;&lt;/tt&gt; 要換成要傳送的群組 &lt;tt class=&quot;docutils literal&quot;&gt;chat_id&lt;/tt&gt;，而 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;$telegram-token&lt;/span&gt;&lt;/tt&gt; 則是我定義的變數，值設定在 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;argocd-notifications-secret&lt;/span&gt;&lt;/tt&gt;）&lt;/p&gt;
&lt;p&gt;概念上就是設定推送的必要資訊，如用 Telegram 時就要有機器人的 token。&lt;/p&gt;
&lt;p&gt;再來就是設定驅動的條件，可以針對個別 Application 設定，也可以直接在此設定全域的規則，比如說成功要通知、失敗要通知、狀態會 Unknown 的時候要通知等等。&lt;/p&gt;
&lt;p&gt;至於傳送的訊息本身也是在此設定。如果有看官方的文件，文件上說可以執行：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;apply&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;-n&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;argocd&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;-f&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;https://raw.githubusercontent.com/argoproj/argo-cd/stable/notifications_catalog/install.yaml
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這其實就是定義一些預設的樣版（也就是剛才提的 Template），這樣就不用自己定義訊息內容了。但我認為自己的東西，用自己定義的訊息比較清楚，所以我個人是覺得參考一下，自己生就好，不需要用官方的樣版。&lt;/p&gt;
&lt;p&gt;再來是 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;argocd-notifications-secret&lt;/span&gt;&lt;/tt&gt; 的例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;v1&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;Secret&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;argocd-notifications-secret&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;Opaque&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;stringData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;telegram-token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;&amp;lt;token&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最後將設定部署到 Argo CD 中：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;apply&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;-n&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;argocd&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;-f&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;argocd-notifications-cm.yaml&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;-f&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;argocd-notifications-secret.yaml
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;搞定。&lt;/p&gt;
&lt;p&gt;好吧，其實沒搞定。&lt;/p&gt;
&lt;p&gt;看起來很簡單，但實際使用的時候還是遇到了問題。&lt;/p&gt;
&lt;p&gt;不知道為何我一直沒有成功收到 Telegram 的通知，原本我以為是我哪些設定有問題，或是有什麼功能要啟用之類的？&lt;/p&gt;
&lt;p&gt;經過研究後，我發現 Argo CD 應該是有成功驅動通知，但不知為何一直沒有辦法成功送出。&lt;/p&gt;
&lt;p&gt;我猜有可能是 Argo CD 可能只支援 Telegram 特定類型的對話，我原本是設定 Telegram Bot 的 chat_id，想讓它直接傳訊息給我，既然一直沒成功，我決定建立一個新的 Telegram 群組，改成用群組的 chat_id （格式為 &lt;tt class=&quot;docutils literal&quot;&gt;-&lt;/tt&gt; 開頭的一串數字）試試，結果這次就真的成功了。&lt;/p&gt;
&lt;p&gt;完美。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[隨手記 - 重新開始]]></title>
        <id>https://blog.marco79423.net/articles/%E9%9A%A8%E6%89%8B%E8%A8%98-%E9%87%8D%E6%96%B0%E9%96%8B%E5%A7%8B/</id>
        <link href="https://blog.marco79423.net/articles/%E9%9A%A8%E6%89%8B%E8%A8%98-%E9%87%8D%E6%96%B0%E9%96%8B%E5%A7%8B/"/>
        <updated>2023-03-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[沈澱許久，前後也搞了多個網站，像是 啪唧工具包、 西卡神教福音、Jessiclient 和等等。
過去我不停地開發新專案，累積了不少學習心得。只是因為內容太雜，不成體系，覺得不夠格寫一篇技術文章，因此只好先放著。
時間一長，我也漸漸忘記這些經驗 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;沈澱許久，前後也搞了多個網站，像是 &lt;a class=&quot;reference external&quot; href=&quot;http://toolset.marco79423.net/&quot;&gt;啪唧工具包&lt;/a&gt;、 &lt;a class=&quot;reference external&quot; href=&quot;https://jessigod.marco79423.net/&quot;&gt;西卡神教福音&lt;/a&gt;、&lt;a class=&quot;reference external&quot; href=&quot;https://jessiclient.marco79423.net/&quot;&gt;Jessiclient&lt;/a&gt; 和等等。&lt;/p&gt;
&lt;p&gt;過去我不停地開發新專案，累積了不少學習心得。只是因為內容太雜，不成體系，覺得不夠格寫一篇技術文章，因此只好先放著。&lt;/p&gt;
&lt;p&gt;時間一長，我也漸漸忘記這些經驗，雖然自認在技術方面也鑽研出不少東西，但因為沒有記錄，所以就這樣消逝了。&lt;/p&gt;
&lt;p&gt;明明花費了許多時間，最後卻沒有成功累積東西，這讓我感到非常可惜。&lt;/p&gt;
&lt;p&gt;直到某天，我突然領悟其實不用非要等到有足夠積累才能發表，我完全可以分開進行。雖然我個人還是希望自己分享的技術文章有一定的品質，但我並不是一定只能寫技術文章對吧？&lt;/p&gt;
&lt;p&gt;我完全可以在有一些小想法或小心得時就發表「非技術分享」的短文。&lt;/p&gt;
&lt;p&gt;而這些短文累積起來，如果有足夠的內容，就可以整理成真正的技術文章。同樣的道理，之後如果能累積多篇有價值的技術文章，甚至可以整理成一個專題也無不可。&lt;/p&gt;
&lt;p&gt;基於這個想法，我決定重新整理網站架構，分為幾個大分類：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;《隨手記》&lt;/p&gt;
&lt;p&gt;概念是平常有什麼心得或是開發紀錄就隨手記下來下來。會有學習心得、想法短文、技術探索這幾種類型，內容會偏向和自我的對話，就像是日記一般，算是一種紀錄，也是上面說的「短文」。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;《技術分享》&lt;/p&gt;
&lt;p&gt;單篇技術文章分享，讓讀者覺得有價值、有收穫的內容。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;《技術專題》&lt;/p&gt;
&lt;p&gt;彙整過去的單篇技術分享文章，並補充內容成有系統的專題。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接下來，我會往幾個方向探索，分別是：我本業的網站開發、和延伸 SEO 相關研究、再來就是很夯的 AI 和我個人有興趣的投資相關的內容。&lt;/p&gt;
&lt;p&gt;希望一切順利。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何從 PCHome 轉移域名至 Godaddy？]]></title>
        <id>https://blog.marco79423.net/articles/%E5%A6%82%E4%BD%95%E5%BE%9E-pchome-%E8%BD%89%E7%A7%BB%E5%9F%9F%E5%90%8D%E8%87%B3-godaddy/</id>
        <link href="https://blog.marco79423.net/articles/%E5%A6%82%E4%BD%95%E5%BE%9E-pchome-%E8%BD%89%E7%A7%BB%E5%9F%9F%E5%90%8D%E8%87%B3-godaddy/"/>
        <updated>2022-06-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[多年來，一直覺得 PCHome 的網域很貴，所以時常在考慮換別的域名註冊商。
最近我有了一個小發現，所以終於決定下定決心換到 GoDaddy。而我也注意到網路上好像沒有什麼關於從 PCHome 轉網域到 GoDaddy 的教學文章，所以來分享一下，順便做個紀錄 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;多年來，一直覺得 PCHome 的網域很貴，所以時常在考慮換別的域名註冊商。&lt;/p&gt;
&lt;p&gt;最近我有了一個小發現，所以終於決定下定決心換到 GoDaddy。而我也注意到網路上好像沒有什麼關於從 PCHome 轉網域到 GoDaddy 的教學文章，所以來分享一下，順便做個紀錄。&lt;/p&gt;
&lt;p&gt;至於那個「小發現」是啥？其實是因為很早以前，我嘗試要轉移網域時，在 PCHome 的 &lt;a class=&quot;reference external&quot; href=&quot;http://extra.pchome.com.tw/adm/faq_content02.htm?channel_no=Is2WXMu..ANNAv3-d1BLNcX__&amp;amp;exh_no=P000007&amp;amp;pexh_no=P000183&amp;amp;cexh_no=H000228&amp;amp;faq_id=1461&amp;amp;qnum=8#0&quot;&gt;常見問題&lt;/a&gt; 中看見：&lt;/p&gt;
&lt;img alt=&quot;1.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244f980e-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
都符合上述條件，請填妥「網域名稱轉出申請書」及附上申請書上所列之相關文件，再傳真給本公司。
&lt;/pre&gt;
&lt;p&gt;沒錯！填本身申請書不是什麼過份的要求，但竟然需要用「傳真」！？&lt;/p&gt;
&lt;p&gt;不是吧？這年頭還有人在用傳真的嗎？難道這其實是令人打退堂鼓的手段？&lt;/p&gt;
&lt;p&gt;我不知道是否真有這個目的，但這確實是多年我想換結果沒換的原因。&lt;/p&gt;
&lt;p&gt;直到某天，我隨手下載網頁的「網域名稱轉出申請書」查看，才發現原來申請書裡頭有說明其實可以直接用檔案上傳的方式，並非一定要用傳真，這才讓我下定決心來轉移。&lt;/p&gt;
&lt;img alt=&quot;2.png&quot; src=&quot;https://blog.marco79423.net/backend/static/24503912-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;（順帶一提，「檔案上傳」的連結其實就是連絡客服頁面，具體等會兒會提到）&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h2&gt;流程說明&lt;/h2&gt;
&lt;p&gt;這邊簡單介紹一下轉移所需的條件，由於大部分的網域名稱都有支援網域名稱移轉鎖定，意思是域名上會有一個鎖定的狀態。鎖定後域名就無法移轉至其他註冊商 （經銷商）。&lt;/p&gt;
&lt;p&gt;所以要轉移之前就必須先在原註冊商網站申請轉出，請舊註冊商解除鎖定，具體而言就是請他們將移轉密碼（EPP Code）寄給你。&lt;/p&gt;
&lt;p&gt;「移轉密碼」各家似乎都有不同的說法，如 EPP 碼、EPP 移轉碼、移轉金鑰、授權碼等等，簡單來說就是要轉到別的註冊商時必須要有的密碼。有了這個密碼，新的註冊商才能取得你原網域的使用權。&lt;/p&gt;
&lt;p&gt;因此在轉網域前，必須要先申請取得這個密碼，然後才到新註冊商去申請轉入，大致流程就是這樣。&lt;/p&gt;
&lt;p&gt;接下來我就來詳細分享轉移的過程，正式開始我們的轉移之旅！&lt;/p&gt;
&lt;p&gt;＊＊＊&lt;/p&gt;
&lt;p&gt;首先，因為要取得移轉密碼，所以要去 PCHome 填申請表，剛才有說過可以去的 &lt;a class=&quot;reference external&quot; href=&quot;http://extra.pchome.com.tw/adm/faq_content02.htm?channel_no=Is2WXMu..ANNAv3-d1BLNcX__&amp;amp;exh_no=P000007&amp;amp;pexh_no=P000183&amp;amp;cexh_no=H000228&amp;amp;faq_id=1461&amp;amp;qnum=8#0&quot;&gt;常見問題&lt;/a&gt; 的頁面下載。&lt;/p&gt;
&lt;p&gt;（順帶一提，申請表是古早的 .doc 檔，因為我沒有 Word，所以我是扔到 Google Doc 打開的）&lt;/p&gt;
&lt;p&gt;內容大致就是填一些基本聲明而已，不會太困難。&lt;/p&gt;
&lt;img alt=&quot;3.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244ea52a-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;搞定之後，可以去 &lt;a class=&quot;reference external&quot; href=&quot;http://extra.pchome.com.tw/adm/customer_service.htm&quot;&gt;聯絡客服&lt;/a&gt; 填寫申請，並上傳申請表和身份證正反面影本。&lt;/p&gt;
&lt;p&gt;（記得要提供註冊人的身份證正反面影本，不然會被打槍，不要問我為啥知道）&lt;/p&gt;
&lt;img alt=&quot;4.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244edbe4-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;填完之後約一天，就會收到客服中心的回覆說收到了，並表示之後會再寄一封信告訴我亂數移轉密碼（也就是剛才說的移轉密碼）。&lt;/p&gt;
&lt;img alt=&quot;5.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244d3988-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;也就是下面這封信，那個 Authorization Information 後面的即是移轉密碼，可以在之後去新註冊商轉入域名的時候使用。&lt;/p&gt;
&lt;img alt=&quot;6.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244e6c86-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;接著就是到  GoDaddy 申請移轉。&lt;/p&gt;
&lt;p&gt;老實說我覺得 GoDaddy 的操作比 PCHome 還不直覺， PCHome 的介面醜歸醜，至少我使用沒啥大問題，也不會有一堆莫名其妙的廣告，但在 GoDaddy 就複雜許多，所以這裡簡單指引一下要怎麼點進去。&lt;/p&gt;
&lt;img alt=&quot;7.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244f4750-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;接著選轉移網域名稱。&lt;/p&gt;
&lt;img alt=&quot;8.png&quot; src=&quot;https://blog.marco79423.net/backend/static/24500082-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;輸入要轉的網域。&lt;/p&gt;
&lt;img alt=&quot;9.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244d8d66-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;最後就會來到這一頁，它會告訴你轉移所需要的金額並且要求你填入的授權碼（也就是移轉密碼）&lt;/p&gt;
&lt;img alt=&quot;10.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244dc984-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;接著就可以在這裡填入授權碼，搞定！&lt;/p&gt;
&lt;p&gt;順帶一提，其實當下沒填入授權碼也可以過，但如果原網域有鎖定的話，之後還是會收到 GoDaddy 的來信要求補填（像下面這張這樣）。&lt;/p&gt;
&lt;img alt=&quot;11.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244fc932-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;總之，填完移轉碼後，幾天後就會收到確認信。上面會簡單說明說收到這個確認信後可以決定要不要繼續或取消，但如果一段時間都沒回覆就會自動確認。&lt;/p&gt;
&lt;img alt=&quot;12.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244e369e-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;如果你很叛逆，決定不理它的話，它還會再寄一次。&lt;/p&gt;
&lt;img alt=&quot;13.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244dff62-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;總之確認一下就好。&lt;/p&gt;
&lt;img alt=&quot;14.png&quot; src=&quot;https://blog.marco79423.net/backend/static/244f1212-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h2&gt;後記&lt;/h2&gt;
&lt;p&gt;用了一陣子，再次認真覺得好像 PCHome 也沒多差，至少對我來說很好懂，雖然陽春了一點，但還是挺堪用的。&lt;/p&gt;
&lt;p&gt;反之 GoDaddy 每次要操作什麼都要點好多次，還要避過一堆廣告才能點到我想要的東西。我覺得使用體驗實在不怎麼樣。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[淺談 NATS、STAN 和 JetStream 兩三事]]></title>
        <id>https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-natsstan-%E5%92%8C-jetstream-%E5%85%A9%E4%B8%89%E4%BA%8B/</id>
        <link href="https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-natsstan-%E5%92%8C-jetstream-%E5%85%A9%E4%B8%89%E4%BA%8B/"/>
        <updated>2022-01-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[事情是這個發生的……
因為公司新專案需要用到 MQ，所以我們選用了 NATS Streaming (STAN) 當作我們專案的 MQ。
沒想到如火如荼的密集開發一年後，某天在 Github 查看 STAN 的資料時才發現原來 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;事情是這個發生的……&lt;/p&gt;
&lt;p&gt;因為公司新專案需要用到 MQ，所以我們選用了 NATS Streaming (STAN) 當作我們專案的 MQ。&lt;/p&gt;
&lt;p&gt;沒想到如火如荼的密集開發一年後，某天在 Github 查看 STAN 的資料時才發現原來 STAN 要被開發團隊棄用了……&lt;/p&gt;
&lt;img alt=&quot;1.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2567282e-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;（來源: &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/nats-io/nats-streaming-server&quot;&gt;https://github.com/nats-io/nats-streaming-server&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;……三小？&lt;/p&gt;
&lt;p&gt;看上面說明，開發團隊建議改用他們的新產品 JetStream，取代現有的 STAN。&lt;/p&gt;
&lt;p&gt;所以 JetStream 是啥？聽都沒聽過？&lt;/p&gt;
&lt;p&gt;於是決定來研究一下 JetStream 究竟是何方神聖？並藉此機會順便回顧一下先前的 Core NATS 和 NATS Streaming 兩套產品，並且比較一下新舊產品的差異。&lt;/p&gt;
&lt;p&gt;這次分享的主題主要有三個部分：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;一是簡單介紹 Message Queue (MQ) 的概念和其用途；&lt;/li&gt;
&lt;li&gt;二是分別介紹 NATS、NATS Streaming 和這次的重點 JetStream；&lt;/li&gt;
&lt;li&gt;最後再做個簡易的評測。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;section&quot; id=&quot;message-queue-mq&quot;&gt;
&lt;h2&gt;Message Queue (MQ)&lt;/h2&gt;
&lt;p&gt;Message Queue (MQ)，時常翻譯為「訊息佇列」或「消息隊列」，常見的開源選擇有 RabbitMQ、Kafka 和今天要談的 NATS。&lt;/p&gt;
&lt;img alt=&quot;2.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2568382c-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;Message Queue 本身可以簡單想像成是一個服務級別的 Queue，同樣訊息先進先出，差別在因為這是獨立的服務，所以通常必須異步處理；另一個分別是通常 Queue 是一進一出，一則訊息被一個消費者接收，另一個就收不到，但 Message Queue 可以做到讓每個消費者都能收到全部的訊息(這通常是可選的)。&lt;/p&gt;
&lt;p&gt;MQ 概念上大致可以分別兩個角色，分別是：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;生產者 (Producer)&lt;/li&gt;
&lt;li&gt;消費者 (Consumer)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;生產者負責生產訊息 (Message)，並丟進 MQ，而消費者負責接收並處理訊息。兩者可以完全不用知道對方，只要和 MQ 溝通即可。&lt;/p&gt;
&lt;p&gt;換句話說，只要生產者產生的訊息符合消費者能接收的格式，那麼其實不一定具體非得由哪個生產者才能生產。因為無論是單個生產者還是多個不同的生產者，對身為接收端的消費者都無所謂，只要能正常收到符合條件格式的訊息即可。&lt;/p&gt;
&lt;p&gt;反過來也是如此，生產者只管生產，至於後端究竟有多少個消費者消費，對生產者來說無關緊要，也不會影響程式碼，可以讓生產者和消費者各自都擁有最大的彈性。&lt;/p&gt;
&lt;p&gt;套用我流解釋，使用 MQ 可以有幾個好處：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;更簡單&lt;/li&gt;
&lt;li&gt;更可靠&lt;/li&gt;
&lt;li&gt;更大更強&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h3&gt;更簡單 - 程式簡化和解耦&lt;/h3&gt;
&lt;img alt=&quot;3.png&quot; src=&quot;https://blog.marco79423.net/backend/static/25661fba-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;使用 MQ 可以方便讓不同服務解耦，正如前面所說，所有服務不管是生產者還是消費者 ，全部都統一都只和 MQ 溝通，生產者不用管是誰處理或是什麼時候處理，而消費者也不用管是誰生產的內容。&lt;/p&gt;
&lt;p&gt;因此無論是生產者還是消費者都可以自由的拆分成多個服務，讓每個服務都只負責一件事，程式碼可以很單純。&lt;/p&gt;
&lt;p&gt;也就是說只要能和 MQ 溝通，不管是用什麼程式語言、用什麼方式處理皆無所謂，就算後面其實是一隻雞在處理也可以。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
你永遠不會知道網路上和你聊天的是不是一隻雞，但如果他真的能用你理解的方式溝通、協作，那對方是不是只是一隻雞其實也沒差了。
&lt;/pre&gt;
&lt;p&gt;更進一步說，其實連時間也解耦了，因為中間隔了一層 MQ，所以不一定需要生產者和消費者同時在線上。&lt;/p&gt;
&lt;p&gt;生產者在生產訊息時，沒人規定消費者非得即時在線上處理；反之亦然，消費者在處理訊息時，生產者也不一定要同時在線上生產訊息。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h3&gt;更可靠 - 服務掛掉也沒差的能力&lt;/h3&gt;
&lt;img alt=&quot;4.png&quot; src=&quot;https://blog.marco79423.net/backend/static/256655ac-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;在這個架構下，因為生產者或消費者不需要直接連結，所以即使服務掛掉，系統還能一定程度的繼續運作。&lt;/p&gt;
&lt;p&gt;因為 MQ 通常都有一定程度的儲存訊息的能力，所以即使某一個消費者掛掉，也可以等到它復活後再繼續把之前沒推送成功的訊息再推送給它。&lt;/p&gt;
&lt;p&gt;雖然處理時間多少會受影響，但至少訊息不會掉，在多數情境下，這樣也不會影響到系統的運作。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-3&quot;&gt;
&lt;h3&gt;更大更強 - 大流量的緩衝&lt;/h3&gt;
&lt;img alt=&quot;5.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2564744e-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;網路服務的流量並不一定是恆定的，系統有時可能會突然面臨超大量的網路請求，但是即使要開更多台服務器也需要一點時間，這時 MQ 就可以當作「漏斗」一樣的功能，充當緩充。&lt;/p&gt;
&lt;p&gt;等到足夠數量的服務啟動完畢，可以跟上訊息生產的速度了，就可以處理之前來不及處理的訊息了。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;另一種假設是這類超大量的網路請求不會一直持續，所以如果業務許可，也可以選擇不啟動新的服務器，讓 MQ 先接收下來就好，之後再讓消費者慢慢消化，用時間換取資源，也是一種選擇。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;core-nats&quot;&gt;
&lt;h2&gt;Core NATS&lt;/h2&gt;
&lt;img alt=&quot;6.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2565ea54-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;這裡首先介紹 Core NATS，此處有個很容易混淆的點是 NATS 其實有三項產品：一是 Core NATS、另一是 NATS Streaming，最後還有最新的 JetStream。&lt;/p&gt;
&lt;p&gt;因為官網的文件是直接把這三者的內容寫在一起，所以一開始沒看清楚很容易會以為是同樣的東西。但其實這是各自擁有不同概念的三項產品。&lt;/p&gt;
&lt;p&gt;Core NATS（以下簡稱 NATS），一個開源、雲原生、用 Golang 寫的訊息傳遞系統，也是 NATS 最基礎的產品。&lt;/p&gt;
&lt;p&gt;NATS 使用「發布」和「訂閱」的方式和程式溝通，並且不做任何持久性的處理，非常單純，所以效能也相當好。&lt;/p&gt;
&lt;p&gt;程式操作也非常簡單，這裡介紹一下簡單的例子。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;nats&quot;&gt;
&lt;h3&gt;範例 - NATS 連線&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 連線&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;nats://localhost:4222&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;連不上 NATS&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;nats-1&quot;&gt;
&lt;h3&gt;範例 - NATS 發送訊息&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 發送訊息&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;subject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;Hello world&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;送不出去&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 清空緩衝&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Flush&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;清空失敗&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;基於效能因素，函式庫會先緩充要發送的訊息，直到一定量的時候才會發送。但如果應用程式有需求要確保想處理的已經確實處理好了，可以使用 flush 函式立即處理。&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;(來源：&lt;a class=&quot;reference external&quot; href=&quot;https://docs.nats.io/developing-with-nats/sending/caches&quot;&gt;https://docs.nats.io/developing-with-nats/sending/caches&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;nats-2&quot;&gt;
&lt;h3&gt;範例 - NATS 接收訊息&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 接收訊息&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;subject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;收到了&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;訂閱失敗&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;queue-groups&quot;&gt;
&lt;h3&gt;Queue Groups&lt;/h3&gt;
&lt;p&gt;NATS 內建 Load balancing 的功能，你可以在訂閱的時候隨便指定一個 Queue 的名稱，NATS 會確保訊息能自動分配訊息到同一個 Queue Group 的不同的消費者。&lt;/p&gt;
&lt;img alt=&quot;7.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2566f1e2-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 訂閱時直接指定 Queue 的名稱，不需要用設定檔預先設定&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;QueueSubscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;subject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;queue&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;收到了&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;訂閱失敗&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這樣一來，即使一個 Subject 有多個消費者一起處理，每則訊息也只會被處理一次，非常方便。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-4&quot;&gt;
&lt;h3&gt;「最多一次」交付模型&lt;/h3&gt;
&lt;p&gt;前面說到，NATS 本身不做任何持久性處理，換句話說，如果 NATS 發送訊息的時候如果沒人接，掉了就掉了， NATS 也不會理它，訊息便有可能會丟失。&lt;/p&gt;
&lt;img alt=&quot;8.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2568012c-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;雖然 NATS 本身不管這件事，但它提供了一種稱為 Request / Reply 的解決方案。&lt;/p&gt;
&lt;p&gt;簡單來說就兩件事：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;消費者主動回傳「收到」的訊息給生產者知道有收到&lt;/li&gt;
&lt;li&gt;讓開發者自行解決有沒有收到的問題&lt;/li&gt;
&lt;/ul&gt;
&lt;img alt=&quot;9.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2564e1d6-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;request-reply&quot;&gt;
&lt;h3&gt;範例 - Request / Reply 機制&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 自動建立一個唯一 subject&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;reply&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NewInbox&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 發送訊息&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PublishRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;subject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;Hello world&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;送不出去&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;此例的 reply 其實就是一個普通的 Subject，可以正常透過 Subscribe 監聽，用來接收消費者回傳的「收到」訊息。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 接收訊息&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;subject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;收到了&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;我收到了&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 生產者會監聽 reply，來確認消費者有沒有收到&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;訂閱失敗&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Respond 意思就是回傳訊息給上例的 reply，只要生產者有監聽，就可以收到該訊息。藉此讓生產者自行處理「收到」或是「沒收到」的情況。&lt;/p&gt;
&lt;p&gt;當然，這是有需要判斷有沒有收到的場景才需要這麼做，如果本身是不介意掉訊息的場景那就沒差了。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;nats-3&quot;&gt;
&lt;h3&gt;NATS 小結&lt;/h3&gt;
&lt;p&gt;簡單來說，Core NATS 的好處就是速度很快，簡單易用，而且對我來說因為是用 Golang 寫的，有什麼問題比較容易自己找到原因。&lt;/p&gt;
&lt;p&gt;但因為有可能因為以消費者掛掉而掉訊息，所以適合需要大量、低延遲的場景 (比 Kafka 低很多)，而且不擔心漏訊息的場景 (或是能自行維護也行)。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;nats-streaming-stan&quot;&gt;
&lt;h2&gt;NATS Streaming (STAN)&lt;/h2&gt;
&lt;p&gt;NATS Streaming，縮寫為 STAN，與前述的 Core NATS 相比，最重要的就是新增了持久化的功能，可以說就是「有持久化功能的 NATS」。&lt;/p&gt;
&lt;p&gt;具體的應用場景，大約有下列四種情況：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;需要訊息的歷史紀錄 (需要 Replay data 的時候)&lt;/li&gt;
&lt;li&gt;Producer 和 Consumer 高度解偶，有可能不是同時在線&lt;/li&gt;
&lt;li&gt;Producer 和 Consumer 需要按照自己的節奏發送、或是接收資料&lt;/li&gt;
&lt;li&gt;最後一條訊息對 Consumer 是必須的 (Producer 可能離線)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根據官方的說法，其實大部分用原始的 NATS 即可，如果要確保收到，可以透過前述的 Request / Reply 機制解決，官方相信自行在應用端管理，長久下來會比直接用 STAN 更加穩定。&lt;/p&gt;
&lt;p&gt;(當然啦，身為苦逼的開發者，不一定都有機會可以能長遠的看待問題就是了……)&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;stan&quot;&gt;
&lt;h3&gt;獨立的 STAN&lt;/h3&gt;
&lt;p&gt;雖然乍聽起來， STAN 好像只是 NATS 多了持久化的功能而已，但其實兩者幾乎是完全不同的東西， STAN 有完全屬於自己的概念，有自己獨立的函式庫，只是函式庫內部使用 NATS 連線而已。&lt;/p&gt;
&lt;img alt=&quot;10.png&quot; src=&quot;https://blog.marco79423.net/backend/static/256547a2-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;由於 STAN 只是將 NATS 當作連線工具使用，會用自己的方式將資訊做包裝，如果你直接透過 NATS 來觀察，你發現完全看不出什麼鬼。&lt;/p&gt;
&lt;p&gt;簡單來說， STAN 與 NATS 是不同的東西。&lt;/p&gt;
&lt;p&gt;好比說客戶端在連上 STAN 需要指定使用的 Cluster ID，也要自行指定自己的 Client ID。&lt;/p&gt;
&lt;p&gt;而這邊的 Client ID 是專屬於 NATS Streaming 的概念，並不是 NATS 的 Client ID (但因為是用 NATS 連線，所以同時仍然也會有 NATS 的 Client ID)。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;stan-1&quot;&gt;
&lt;h3&gt;範例 - STAN 連線&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 連線&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;stanConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;test-cluster&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// Cluster ID&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;clientID&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 客戶端自設的 Client ID&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NatsURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;nats://localhost:4222&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NatsOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;NATS 連線名稱&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;連不上 STAN&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stanConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;此外， STAN 使用的是 Channel 而非 Subject，雖然看似相同，但實際卻有差別。&lt;/p&gt;
&lt;p&gt;NATS 原生的 Subject 可以支援 wildcard，我們可以在直接訂閱 &lt;cite&gt;chicken.*&lt;/cite&gt; ，那麼無論是 &lt;cite&gt;chicken.a&lt;/cite&gt; 又或是 &lt;cite&gt;chicken.b&lt;/cite&gt; 也都能收到訊息，但 NATS Streaming 的 Channel 就不支援這麼做。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;不知為何，雖然官網說 STAN 用的是 Channel 而非 Subject，但函式庫的命名還是使用 subject&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;(來源： &lt;a class=&quot;reference external&quot; href=&quot;https://docs.nats.io/developing-with-nats-streaming/streaming&quot;&gt;https://docs.nats.io/developing-with-nats-streaming/streaming&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;而 STAN 使用的訊息也是不同的物件，一個是 &lt;tt class=&quot;docutils literal&quot;&gt;nats.Msg&lt;/tt&gt; 另一個是 &lt;tt class=&quot;docutils literal&quot;&gt;stan.Msg&lt;/tt&gt; 。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;stan-2&quot;&gt;
&lt;h3&gt;範例 - STAN 發送訊息&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 發送訊息&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stanConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;channel&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;Hello world&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;送不出去&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;stan-3&quot;&gt;
&lt;h3&gt;範例 - STAN 接收訊息&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 接收訊息&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stanConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;channel&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 使用 stan.Msg&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;收到了&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;訂閱失敗&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;durable&quot;&gt;
&lt;h3&gt;Durable&lt;/h3&gt;
&lt;p&gt;因為 STAN 多了持久化的功能，所以消費者端這邊就不用在生產者生產訊息的時候即時接收，只要訊息還存在 STAN 裡，就可以自行選擇任意時間和位置開始接收訊息。&lt;/p&gt;
&lt;p&gt;但是如果消費者端每次都要隨時自己記得自己收到哪裡也很麻煩，所以 STAN 也多了 Durable 的概念。&lt;/p&gt;
&lt;p&gt;STAN 本身會幫忙記錄消費者收到哪裡，如果消費者斷線回復，STAN 會自動從斷線的地方開始送。&lt;/p&gt;
&lt;p&gt;消費者可以在訂閱的時候指定 Durable 名稱，STAN 會把消費者的 Client ID 和 Durable 當作 Key 記錄當前接收到的位置。假若消費者因故斷線重連，那麼 STAN 就會根據 Client ID 和 Durable 判斷從哪個位置開始發送。&lt;/p&gt;
&lt;p&gt;由於 STAN 也支援前述的  Queue Groups 的功能，所以 STAN 的訂閱其實有四種組合，分別為：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;50%&quot; /&gt;
&lt;col width=&quot;50%&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead valign=&quot;bottom&quot;&gt;
&lt;tr&gt;&lt;th class=&quot;head&quot;&gt;類型&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;Regular&lt;/td&gt;
&lt;td&gt;最基本的訂閱模式，當應用關掉、取消訂閱時，就會失去位置，下次訂閱需要重新指定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Durable&lt;/td&gt;
&lt;td&gt;消費者斷線時會保留位置，下次訂閱還會從上次最後接收的位置開始 (不包含主動取消訂閱)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Queue&lt;/td&gt;
&lt;td&gt;多個消費者共享位置，但全部斷線就會失去位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Durable / Queue&lt;/td&gt;
&lt;td&gt;多個消費者共享位置，但即使全部斷線也不會失去位置 (除非最後一個主動取消訂閱)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;簡單來說， Durable 就是保留位置，而 Queue 就是共用位置，兩兩相乘就是四種可能。&lt;/p&gt;
&lt;p&gt;至於 Durable 和 Durable / Queue 的差別在於前者以 ClientID 和 Durable 為 Key 記錄最後收到的訊息位置，而後者則是以 Queue 和 Durable 為 Key 來記錄。所以對於前者來說，如果不同 ClientID ，就會各自當不同的訂閱，而後者則會共用同一個。&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;50%&quot; /&gt;
&lt;col width=&quot;50%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;Durable&lt;/td&gt;
&lt;td&gt;Server 會維護一份訂閱紀錄 (ClientID + Durable 為 Key) 記錄最後收到的訊息位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Durable / Queue&lt;/td&gt;
&lt;td&gt;Server 同樣會維護一訂閱紀錄 (Queue + Durable 為 Key) 記錄最後收到的訊息位置 (這種情況下 ClientID 不重要)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;(來源：&lt;a class=&quot;reference external&quot; href=&quot;https://github.com/nats-io/nats-streaming-server/issues/723#issuecomment-452361690&quot;&gt;https://github.com/nats-io/nats-streaming-server/issues/723#issuecomment-452361690&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-5&quot;&gt;
&lt;h3&gt;「至少一次」交付模型&lt;/h3&gt;
&lt;p&gt;如果說 NATS 提供的是「最多一次」的交付模型，那麼 STAN 就是「至少一次」的交付模型，因為多了持久化的功能，所以 STAN 可以保留之前的訊息，如果消費者端沒收到就自動重送。&lt;/p&gt;
&lt;p&gt;而為了確認消費者端有沒有收到訊息，所以 STAN 也多了 Ack 的概念，讓消費者端可以回報 STAN 說這個訊息處理成功了。如果 STAN 這端等太久沒收到 Ack，就會認為消費者沒有收到訊息而進行重送。&lt;/p&gt;
&lt;p&gt;有時因為一些網路的原因，有可能會發生 STAN 認為消費者端沒收到，但其實有的情況，好比說消費者的 Ack 太慢送，導致 STAN 發生 Timeout 認為沒送到再送一次。一旦發生這種情況，相同的訊息就有可能會重送，所以實作上要設計成冪等的，系統要支持重複的訊息而不會發生錯誤才行。&lt;/p&gt;
&lt;img alt=&quot;11.png&quot; src=&quot;https://blog.marco79423.net/backend/static/25675c4a-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;我們可以自行選擇使用自動 Ack 或是手動 Ack ，預設是自動 Ack，所以只要有正常收到，基本就當你成功了。&lt;/p&gt;
&lt;p&gt;但我們通常不會把「收到訊息」就當作成功，而是把訊息當作一個「任務」，必須成功做完某件事才當作成功，不然就都算失敗，需要重做。&lt;/p&gt;
&lt;p&gt;所以實務上通常會建議用手動，這樣才能確保自己能控制這個任務究竟是成功還是失敗。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SubscriptionOption&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SetManualAckMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 手動 Ack 模式&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stanConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;channel&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;收到了&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Ack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 手動 Ack&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;訂閱失敗&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;某種程度而言，其實 STAN 就是在 Core NATS 之上再做了 Request / Reply 的功能。 Ack 就是類似 Reply 的效果。而原始的 Core NATS 如果沒收到 Reply，生產者端通常能做的就是重送，而 STAN 接手了這件事情，代替生產者端做同樣的事情。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;訂閱本身不影響 Channel 保留的內容， Ack 完的訊息也不會因此被刪掉&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;stan-4&quot;&gt;
&lt;h3&gt;STAN 的坑&lt;/h3&gt;
&lt;p&gt;聽起來 STAN 似乎很美好，但實際使用時其實有很多坑，剛剛提到 STAN 其實是一個獨立的服務，它有自己的術語，有自己的函式庫，只是把 NATS 當作系統的底層。&lt;/p&gt;
&lt;p&gt;我覺得概念本身沒問題，但問題是 STAN 並沒有完全把 NATS 隱藏起來，從之前的例子可知如果要調整一些設定，還是得引入 NATS 的函式庫，我認為這不是好的設計。&lt;/p&gt;
&lt;p&gt;前面說過，NATS 有 Client ID (由服務端分配)，STAN 也有 Client ID (由客戶端自行指定)，STAN 沒能做到完全隱藏 NATS 的 Client ID，所以就會讓使用者感到困惑。&lt;/p&gt;
&lt;p&gt;在連線的時候，如果要調整參數，還是得引入 NATS 的函式庫，沒辦法只用 STAN 的函式庫就好。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 連線&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;stanConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;test-cluster&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;clientID&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NatsURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;nats://localhost:4222&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NatsOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;NATS 連線名稱&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 這項設定需要引入 nats 函式庫&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;連不上 STAN&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stanConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述的問題可能影響不大，但由於 STAN 和 NATS 是各自獨立的服務器，而且連結並沒有想像中緊密，好比說 NATS 和 STAN 兩者各自都有自己的斷線判斷，而最糟糕的是－－兩者判斷可能不同。&lt;/p&gt;
&lt;p&gt;有可能 NATS 認定斷線，但 STAN 沒有；又或是相反，STAN 認定斷線，但 NATS 沒有。這時就會碰到很大的麻煩，有可能會發生表面上 NATS 還在連線，但其實沒有辦法收到任何訊息的狀況。&lt;/p&gt;
&lt;p&gt;簡單來說，就是它本身的斷線重連機制根本沒辦法正常運作，無法做到用戶無感知，必須自行處理，自行重新訂閱才行。這件事一直在我寫這篇文章時似乎都沒有好的解法，我目前的做法就是只要偵測到斷線，就直接整個重連(包含 NATS 和 STAN 的連線)。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;stan-5&quot;&gt;
&lt;h3&gt;STAN 小結&lt;/h3&gt;
&lt;p&gt;這邊做個簡單的小結，STAN 就是有持久化功能的 NATS，效能也相當不錯，延遲同樣比 Kafka 好，但因為最初設計的一些原因，所以也帶來了許多的坑。&lt;/p&gt;
&lt;p&gt;使用上其實沒有太大的問題，除了……被開發團隊放生以外？&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;jetstream&quot;&gt;
&lt;h2&gt;JetStream&lt;/h2&gt;
&lt;p&gt;最後則是本篇的重頭戲－－JetStream。&lt;/p&gt;
&lt;p&gt;它是開發團隊用來取代 STAN 的新方案，所以也提供了 STAN 類似的功能，但功能更豐富也更強大，同時還修正了 STAN 碰到的問題。&lt;/p&gt;
&lt;p&gt;這次的 JetStream 不再和 STAN 一樣是獨立的服務，而是 NATS 本身的子系統，第一個顯而易見的好處不用再分別啟動 NATS 和 STAN 不同的服務器，只要在 NATS 的服務器簡單加了一個參數就可以用 JetStream 了，可以顯著的減少維運的成本。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;docker&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;run&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;nats:2.6.1&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;-js&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 加上 -js 即可支援 JetStream&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在開發上，也不用再引入不同的函式庫，直接使用 NATS 本身的函式庫就好。如果要使用 JetStream，只要在 NATS 連線的基礎上直接取得 JetStream 的 Context 即可，非常簡單。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;jetstream-context&quot;&gt;
&lt;h3&gt;範例 - 取得 JetStream 的 Context&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 連線&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;nats://localhost:4222&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;連不上 NATS&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 取得 JetStream 的 Context&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;JetStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;取得 JetStream 的 Context 失敗: %v&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;同時 JetStream 也帶來更多更強大的功能，比如可以更細緻的調整訊息的保留方式，除了可以像 STAN 一樣定義訊息的保留時間、大小、數量外，還可以進一步設定「如果沒 Ack 過就永久保留」或是「沒有任何訂閱就刪除」等更進階的功能。&lt;/p&gt;
&lt;p&gt;而且訊息接收方式除了能由 JetStream 主動推訊息外，還多了可以讓消費者自行拉取訊息的模式。&lt;/p&gt;
&lt;p&gt;使用上，JetStream 明確定義了兩個新概念：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;Stream - 負責管理存儲&lt;/li&gt;
&lt;li&gt;Consumer - 負責管理消費&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;stream&quot;&gt;
&lt;h3&gt;Stream&lt;/h3&gt;
&lt;p&gt;Stream 定義了 NATS 訊息保留的規則，如果一條 NATS 訊息符合 Stream 設定的 Subject，就會被 JetStream 存下來。而 JetStream 就是透過管理 Stream 間接做持久化。&lt;/p&gt;
&lt;p&gt;我們可以設定多個不同的 Stream，來對應多個 Subject，同時每個 Stream 也可以支援不同的存儲規則，像是可以自行選擇訊息保留的方式、丟棄的方式等。&lt;/p&gt;
&lt;img alt=&quot;12.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2565b2be-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;而這一切都不需要預先定義，可以在程式運作的過程中動態產生。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;stream-1&quot;&gt;
&lt;h3&gt;範例 - 動態建立新的 Stream&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 建立 Stream&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;AddStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;StreamConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;Stream名稱&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Subjects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;subject.*&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 支援 wildcard&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Storage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;FileStorage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;     &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 儲存的方式 (預設 FileStorage)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Retention&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;WorkQueuePolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 保留的策略&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Discard&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DiscardOld&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 丟棄的策略&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;建立 Stream 失敗: %v&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;而代價則是開發者需要在程式裡顯式管理 Stream，無論是發送和接收，Subject 都必須確保有對應的 Stream 存在，不然就會報錯。&lt;/p&gt;
&lt;p&gt;所以開發者一開始第一個可能碰到的坑，就是想如同用 NATS 一樣直接推送一則訊息，然後就會發現會因為沒有對應的 Stream 而推送失敗。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;補充： JetStream 和 NATS 相同，Subject 都能支援 wildcard，因為 JetStream 的訊息其實就是 NATS 的訊息，當然可以支援。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;consumer&quot;&gt;
&lt;h3&gt;Consumer&lt;/h3&gt;
&lt;p&gt;Consumer 則是定義了消費者接收的規則，消費者在訂閱某個 Subject 時，會自動產生對應的 Consumer。 Consumer 會包含相關的設定，同時還會維護一份紀錄，記錄消費者接收到的位置。&lt;/p&gt;
&lt;img alt=&quot;13.png&quot; src=&quot;https://blog.marco79423.net/backend/static/25657d76-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;JetStream 同樣也有  Durable 的概念，用法和 STAN 也基本相同，差別在於 JetStream 明確定義了 Consumer 的概念，所以對於 JetStream 來說，一個 Durable 就代表一個 Consumer。&lt;/p&gt;
&lt;p&gt;具體來說就是如果消費者訂閱的時候指定了 Durable Name，那麼 JetStream 就會找尋同樣名稱的 Consumer，如果有，就直接從該 Consumer 記錄的位置開始發送訊息，而不是從頭開始。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;push-pull-subscription&quot;&gt;
&lt;h3&gt;Push / Pull Subscription&lt;/h3&gt;
&lt;p&gt;除此之外， JetStream 還多了 Push 和 Pull 的概念，過去 NATS 和 STAN 都是用 Push 的方式由 MQ 推送訊息給消費者，而 JetStream 則再多了 Pull 的方法，讓消費者可以主動和 MQ 要訊息，更好的區分不同的用途。&lt;/p&gt;
&lt;img alt=&quot;14.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2566bba0-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;簡單來說  Push 的方式就是 JetStream 會不管三七二十一狂推，適合量少需要極低延遲的任務，比如說即時監控，Pull 的話就是由消費者主動拉訊息，適合當 Worker 使用。&lt;/p&gt;
&lt;p&gt;兩種方式比較，雖然 Push 會有更低的延遲，更快的速度，但如果對方收不到這麼快也沒用，還可能被當成 Slow consumer 而被踢掉，所以兩種方式各有用途。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;要減少 Slow consumer 的問題，可以設定 RateLimit 或是直接用 Max Pending 來解決。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;push-subscription&quot;&gt;
&lt;h3&gt;範例 - Push Subscription&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;subject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;收到了&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;訂閱失敗&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;用法和 NATS 的幾乎一模一樣，差別是改用 JetStream 的 Context 來操作 (此例為 js)。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;pull-subscription&quot;&gt;
&lt;h3&gt;範例 - Pull Subscription&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PullSubscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;subject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;durable&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// Pull 模式必須要用 Durable&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;xerrors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;訂閱失敗: %w&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 決定一次收幾條&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;xerrors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;接收失敗: %w&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msgs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;收到了&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Ack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 要手動 Ack&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 Pull 模式，使用差異比較大，消費者要自行主動拉資料，可以決定一次要拉幾條，訂閱的時候必須使用 durable，而且必須強制手動 Ack。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;ack&quot;&gt;
&lt;h3&gt;Ack&lt;/h3&gt;
&lt;p&gt;提到 Ack，JetStream 也帶來了相比 STAN 更豐富的 Ack 機制，除了能回傳代表成功的 Ack，也多了代表失敗的 Nak 或是還沒好的 Progress 等等。&lt;/p&gt;
&lt;p&gt;原本在 STAN 中，如果訊息處理失敗的時候，就只能讓 STAN 等到 Timeout，才能判斷失敗。但現在 JetStream 可以讓消費者主動回傳 Nak，讓服務器能更快知道該訊息處理失敗了。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;ack-1&quot;&gt;
&lt;h3&gt;豐富的 Ack 機制&lt;/h3&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;50%&quot; /&gt;
&lt;col width=&quot;50%&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead valign=&quot;bottom&quot;&gt;
&lt;tr&gt;&lt;th class=&quot;head&quot;&gt;功能&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;簡易說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;AckAck&lt;/td&gt;
&lt;td&gt;搞好了&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AckNak&lt;/td&gt;
&lt;td&gt;沒搞成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AckProgress&lt;/td&gt;
&lt;td&gt;還在搞&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AckNext&lt;/td&gt;
&lt;td&gt;先搞下一個&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AckTerm&lt;/td&gt;
&lt;td&gt;這個我不搞&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;ack-2&quot;&gt;
&lt;h3&gt;多樣的 Ack 策略&lt;/h3&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;50%&quot; /&gt;
&lt;col width=&quot;50%&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead valign=&quot;bottom&quot;&gt;
&lt;tr&gt;&lt;th class=&quot;head&quot;&gt;策略&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;AckExplicit [預設]&lt;/td&gt;
&lt;td&gt;每個訊息都要 Ack (每個都要明確的說搞好了)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AckNone&lt;/td&gt;
&lt;td&gt;不用 ack(不用說搞好了沒)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AckAll&lt;/td&gt;
&lt;td&gt;只需要 ack 最後一筆 (搞了這個，就當已經搞好之前所有的訊息)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-6&quot;&gt;
&lt;h3&gt;「保證一次」交付模型&lt;/h3&gt;
&lt;p&gt;JetStream 和 STAN 提供的都是「至少一次」的交付模型，但在限定條件下，它可以做到「保證一次」，來確保消費者不會收到重覆的訊息。&lt;/p&gt;
&lt;p&gt;具體而言，JetStream 提供了兩種機制來確保「發送端不會重送」並且「接收端不會重收」兩件事，依此做到「保證一次」的效果。&lt;/p&gt;
&lt;p&gt;要保證「發送端不會重送」，JetStream 的做法是讓生產者可以為每一則訊息自行指定「訊息 ID」， JetStream 會負責確保同樣的「訊息 ID」只會送一次。&lt;/p&gt;
&lt;p&gt;簡單來說，它會在送完一筆訊息後，在一定時間內無視之後傳來所有相同「訊息 ID」的訊息，來達成不會重送的要求。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;subject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;Hello world&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;MsgId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;訊息ID&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;送不出去&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;之所以要確保「發送端不會重送」，是因為發送端有可能會因為網路原因，明明訊息有送成功了，但卻沒收到 MQ 回傳的「收到訊息」以為自己沒發送成功，而再送一次的狀況。&lt;/p&gt;
&lt;p&gt;如果沒有讓發送端自行指定訊息 ID，對於 JetStream 來說，它其實無法判斷某一則訊息到底是不是重複的。因為即使是完全同樣的訊息內容，在不同的業務中仍然可能代表不同的訊息，因此是否重複只有開發者才能決定。而 JetStream 的做法就是讓生產者自行決定訊息的 ID，如果是一樣的，就代表同一個訊息，反之則不是。&lt;/p&gt;
&lt;p&gt;第二件事就是要確保「接收端不會重收」，這裡似乎有許多不同的說法，但大概念都是類似，要由消費者端主動確認來解決。&lt;/p&gt;
&lt;p&gt;舉例來說，可以讓消費者透過 AckSync 或是限制時間的 Ack 來向 MQ 確認是否已經有消費者已經收到訊息了。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;subject&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;收到了&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;AckSync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;Ack 沒送成功或是這個訊息 Ack 過了&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class=&quot;docutils literal&quot;&gt;AckSync&lt;/tt&gt; 就是用同步的方式 Ack，也就是讓消費者端在 Ack 的時候能同步確認 MQ 有收到自己的 Ack。&lt;/p&gt;
&lt;p&gt;之所以要這麼做是因為 JetStream 有可能因為網路原因沒收到消費者傳來的 Ack 而以為自己沒成功發送訊息而重送。&lt;/p&gt;
&lt;p&gt;而這件事同樣也只能由消費者端主動確認 MQ 是否有收到自己的 Ack，來確保 MQ 不會因為沒收到消費者端的 Ack 而重送(即使重送了也可以判斷出來)。&lt;/p&gt;
&lt;p&gt;雖說 JetStream 號稱可以做到「只有一次」，不過我覺得這樣的代價似乎過大，每個 Ack 都要雙重確認絕對會顯著拖慢效能，感覺沒有必要強求只有一次。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;jetstream-1&quot;&gt;
&lt;h3&gt;JetStream 的坑&lt;/h3&gt;
&lt;p&gt;說了這麼多 JetStream 的好處，但 JetStream 其實也有很多問題，首先是它是全新的東西，因此可以想見穩定性自然是比較差的，甚至有些語言的實作到目前為止（2022/01/17）都還是 Beta 版。&lt;/p&gt;
&lt;img alt=&quot;15.png&quot; src=&quot;https://blog.marco79423.net/backend/static/256792aa-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;同時也因為是新東西，所以文件也非常少，有時必須要直接去 github 查程式碼才行。&lt;/p&gt;
&lt;img alt=&quot;16.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2567c6bc-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;而且更討厭的是官網 NATS、NATS Streaming 和 JetStream 三套產品全部放在同一份文件裡，而三個工具各有不同的概念，名詞定義也有差異，卻可能共用同樣的名字，所以非常容易混淆。&lt;/p&gt;
&lt;img alt=&quot;17.png&quot; src=&quot;https://blog.marco79423.net/backend/static/25686cc0-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;偏偏 JetStream 也不能只看 JetStream 的文件，因為是共用 NATS 的函式庫，所以許多概念還會延用，所以在查文件的時候就會覺得非常困擾。&lt;/p&gt;
&lt;p&gt;另外雖說共用 NATS 函式庫很方便，但產生的缺點就是不同工具的方法也混雜在一起，好比說 NATS 和 JetStream 的訊息都是共用 &lt;tt class=&quot;docutils literal&quot;&gt;nats.Msg&lt;/tt&gt; ，並沒有明確的分隔，所以可能會寫出令人困惑的程式碼。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;natsConn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Nak&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 這玩意兒是給 JetStream 用的，但也不會報錯&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最後一點則是 JetStream 本身的機制似乎也還沒成熟，光是我剛剛提的「保證一次」這件機制就有不同的說法，而且也都不是很明確，我覺得這也是個很嚴重的問題。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;jetstream-2&quot;&gt;
&lt;h3&gt;JetStream 小結&lt;/h3&gt;
&lt;p&gt;同樣做個小結， JetStream 相比 STAN 確實有許多優勢。&lt;/p&gt;
&lt;p&gt;好比說架設方便，由於 JetStream 是 NATS Server 的子系統，加參數就可以使用，不用付出維護兩套服務器的成本。&lt;/p&gt;
&lt;p&gt;而且 JetStream 不但功能比 STAN 更豐富更強大，而且效能還更好（這個後面會提）。&lt;/p&gt;
&lt;p&gt;並且還因為是直接使用 NATS 本身的函式庫，使用上也比 STAN 簡單，幾乎在各方面都能輾壓 STAN。&lt;/p&gt;
&lt;p&gt;但缺點是因為是新東西，穩定性可能比較差，同時有些語言的實作還是 beta 版，所以文件也很少，有時必須得直接看程式碼。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-7&quot;&gt;
&lt;h2&gt;我流簡易評測&lt;/h2&gt;
&lt;p&gt;這裡我簡單做一個效能評測，因為現在 JetStream 還是很新的東西，可能不穩定，比較沒有參考價值，所以我只挑選幾項我個人比較在意的項目做比較。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;section-8&quot;&gt;
&lt;h3&gt;測試環境&lt;/h3&gt;
&lt;p&gt;三台 Server&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;8 Core CPU&lt;/li&gt;
&lt;li&gt;32GB ram&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-9&quot;&gt;
&lt;h3&gt;發布相關效能比較&lt;/h3&gt;
&lt;p&gt;從下圖可以很明顯的看出 JetStream 的發布效能比較好。&lt;/p&gt;
&lt;img alt=&quot;STAN 和 JetStream 的發布 (Publish) 效能比較&quot; src=&quot;https://blog.marco79423.net/backend/static/2564acfc-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;同時接收效能也是 JetStream 比較佳，同時也有更好的延遲表現。&lt;/p&gt;
&lt;img alt=&quot;STAN 和 JetStream 的接收 (Subscribe) 效能比較.png&quot; src=&quot;https://blog.marco79423.net/backend/static/25668a86-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;img alt=&quot;STAN 和 JetStream 的接收延遲效能比較.png&quot; src=&quot;https://blog.marco79423.net/backend/static/25641d1e-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;p&gt;最後再附上 JetStream 的各種接收方式的效能比較，可以看出 Subscribe 和 Chan Subscribe 差不多，而 Pull Subscribe 則較差，但是會隨著一次取得越多而越快。&lt;/p&gt;
&lt;img alt=&quot;JetStream 的各種接收 (Subscribe) 效能比較.png&quot; src=&quot;https://blog.marco79423.net/backend/static/256513c2-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 100%;&quot; /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-10&quot;&gt;
&lt;h2&gt;結語&lt;/h2&gt;
&lt;p&gt;一個工具，有好有壞，適合自己公司的需求才是最重要的，雖然我個人研究了半天，但基於各種原因，我們最後還是沒用 JetStream。因此這邊單純只是做個簡單的紀錄，分享給有需要的人。&lt;/p&gt;
&lt;p&gt;以上。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-11&quot;&gt;
&lt;h2&gt;參考資料&lt;/h2&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://docs.nats.io/&quot;&gt;NATS Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://github.com/nats-io/nats.go&quot;&gt;nats-io/nats.go: Golang client for NATS, the cloud native messaging system.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.gushiciku.cn/pl/g4zz/zh-tw&quot;&gt;NATS-Server(JetStream)和NATS Streaming Server對比&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.jianshu.com/p/27a49b9d4306&quot;&gt;基于NATS JetStream构建分布式事件流系统&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[淺談 REST API 的設計和規劃]]></title>
        <id>https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-rest-api-%E7%9A%84%E8%A8%AD%E8%A8%88%E5%92%8C%E8%A6%8F%E5%8A%83/</id>
        <link href="https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-rest-api-%E7%9A%84%E8%A8%AD%E8%A8%88%E5%92%8C%E8%A6%8F%E5%8A%83/"/>
        <updated>2021-06-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[工作多年，我一直都很好奇一件事，不知道 REST API 到底有什麼神奇的魔力，為什麼大家會這麼喜歡拿來說嘴？公司的面試考題中，總會有一道考題會問－－請問什麼是 REST API？
究竟 REST API ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;工作多年，我一直都很好奇一件事，不知道 REST API 到底有什麼神奇的魔力，為什麼大家會這麼喜歡拿來說嘴？公司的面試考題中，總會有一道考題會問－－請問什麼是 REST API？&lt;/p&gt;
&lt;p&gt;究竟 REST API 有什麼好？值得大家這麼喜歡在設計 API 的時候，言必稱 RESTful 呢？&lt;/p&gt;
&lt;p&gt;而且最玄幻的是－－明明公司的設計 API 一點都不 RESTful，考這個幹啥？&lt;/p&gt;
&lt;p&gt;雖然由於定義的模糊不清，所以就像每個人心中都有一個哈姆雷特一樣，大家心中也都有屬於自己的 REST API。&lt;/p&gt;
&lt;p&gt;但其實還是有一些基本原則，不是只要用 HTTP 當作 API 就都是 REST API 好嗎？&lt;/p&gt;
&lt;p&gt;因為現在的自己也時常需要面試別人，所以免不了也在思考，如果面試的時候有主考官問我這個問題，我會怎麼答呢？&lt;/p&gt;
&lt;p&gt;而這，就是我寫下這篇筆記的原因。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;rest-api-1&quot;&gt;
&lt;h2&gt;什麼是 REST API？&lt;/h2&gt;
&lt;p&gt;如果面試的時候有主考官問我：「要怎麼用一句話解釋 REST API 呢？」&lt;/p&gt;
&lt;p&gt;我會說：「以『資源』為中心，用 HTTP 方法(Method) 操作，並且符合 HATEOAS 要求設計的 API。」&lt;/p&gt;
&lt;p&gt;所以說關鍵詞是「資源 (Resource)」。&lt;/p&gt;
&lt;p&gt;至於什麼是「資源」？為什麼是「資源」？這個我們等會兒再說，因為詳細解釋 REST API 之前，還是要回到歷史，了解這個玩意兒到底是怎麼出現的？&lt;/p&gt;
&lt;p&gt;它是在 2000 年由 Roy Fielding 提出的想法，全稱叫 Representational State Transfer，翻作「表現層狀態轉換」。&lt;/p&gt;
&lt;p&gt;概念是利用傳統 Web 的特點與服務端溝通，不使用 session，每次請求都得帶上身份認證訊息，除此之外，它幾乎沒有什麼明確的規範，安全特性也都要自行處理，所以你可以說它更像是一種設計風格。&lt;/p&gt;
&lt;p&gt;也因為如此，演變到現在其實也產生了許多歧異，好比說 REST API 是否一定要是 HTTP ？&lt;/p&gt;
&lt;p&gt;光是這一點就有許多不同的意見，有人說是必須，但也有人說並非如此，像是 Google 就搞了一個「REST 風格」的 gRPC 規範。&lt;/p&gt;
&lt;p&gt;又好比提出者 Roy Fielding 曾表示必須要符合 HATEOAS 的才算 REST API，但結果很多大廠設計出的「REST API」其實也沒有完全符合這個要求，大家一樣說自己是 REST API 說得很開心。&lt;/p&gt;
&lt;p&gt;說到底其實我也不覺得完全符合 REST API 的規範就一定是好的，畢竟這仍只是「風格」而非「標準」，沒有說非要遵守 REST 風格才是好的。何況實話說大部分公司設計使用的 API，通常都也只有自己公司內部會使用，所以其實只要能符合自身業務需求，老實說怎麼定也無所謂，畢竟公司的終極目標是賺錢，不是定美美的 API 啊。&lt;/p&gt;
&lt;p&gt;但也不是說 REST API 就一無是處，使用 REST 風格的 API 的一個重大好處是大家都相對熟悉這個規範，一個有良好設計的 REST API 幾乎可以做到給你一個 Endpoint，你就可以自行推斷出所有 API 的用法，這對使用者體驗的加成自然是顯而易見的。&lt;/p&gt;
&lt;p&gt;但話說回來，雖然體驗很好，但可以輕易推斷 API 這一點可能也會加大了被人攻擊成功的風險就是了。&lt;/p&gt;
&lt;p&gt;雖說本來就不應該依賴這種只能祈禱別人不知道 Endpoint 的防禦方式，但畢竟我們不是 Google，能做多少算多少不盡然是壞事，需要我們個別去權衡。&lt;/p&gt;
&lt;p&gt;回到正題，REST 的核心就是將一切事物抽象化成「資源」這個概念，一切的操作皆是以資源為中心，我可以新增它、修改它，甚至刪除它。&lt;/p&gt;
&lt;p&gt;一個 REST 的系統，就是一系列資源的互動，用 URI 代表資源，再透過 HTTP 本身的方法來對資源做操作，舉個例子：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
GET /apis/hens   # 代表取得資源 hens (順帶一提，hen 是母雞的意思)
&lt;/pre&gt;
&lt;p&gt;一般來說資源通常會是名詞（此例是 hens），所有的行為操作都靠 HTTP 方法解決，而不是直接在 URI 裡表示，因此我們不會下面這種方法新增母雞：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
POST  /apis/add-hens
&lt;/pre&gt;
&lt;p&gt;而是會用 POST 動作本身代表「新增」母雞這個概念：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
POST /apis/hens
&lt;/pre&gt;
&lt;p&gt;（再強調一次，我不是說這樣一定比較好，只是 REST API 會這麼做而已）&lt;/p&gt;
&lt;p&gt;這個方法乍看之下簡單直覺，使用並不困難，但實際上這常需要做思維的轉換，畢竟業務邏輯這麼複雜，如何才能透過「資源」將整個業務邏輯包含在裡面？尤其是許多「動作」並不只是單純的新增、修改、刪除，那我又該如何處理呢？&lt;/p&gt;
&lt;p&gt;好比說我想要「餵母雞」，「餵」這個動作顯然就很難簡單歸類成新增、修改、刪除的任何一種。&lt;/p&gt;
&lt;p&gt;這時就必須要進行概念上的轉換，一種可能的方案是將「餵母雞」這件事當成一個「任務」，這樣一來「任務」就是資源，我可以用新增這個「餵母雞任務」來代表「餵母雞」，以此類推。只是個別的轉換簡單，但要讓整個業務全部自洽的轉成這種形式就不見得這麼容易了。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
POST /apis/feed-hen-tasks   # 把「餵母雞任務」當作資源就可以新增了
&lt;/pre&gt;
&lt;p&gt;不過正如我先前所說， REST API 只是「風格」而非「標準」，所以我覺得大部分的情況並不需要如此嚴格要求，允許少數的例外可以顯著減少設計上的難度。&lt;/p&gt;
&lt;p&gt;談到「資源」，很多人在設計這些資源時，會習慣直接和資料庫的資料表(Table)直接做對應，但其實這個作法並不嚴謹，因為資料庫是內部使用的概念，不應該直接暴露給外界。&lt;/p&gt;
&lt;p&gt;由於概念上的「資源」和內部實作的資料結構不一能直接對應，所以資料庫的「資料表」和「資源」也不見得會一樣。兩者有可能沒辦法直接一對一對應，有時一個「資源」可能會同時關聯多個「資料表」，也可能反過來，多個「資源」對應同個「資料表」。&lt;/p&gt;
&lt;p&gt;要記得內部開發者和外部開發者使用者是完全兩個不同的角色，在設計的過程中我們要懂得切換自己的角色，因為兩者思考的角度是不一樣的。&lt;/p&gt;
&lt;p&gt;內部開發者容易開發使用不代表外部開發者容易串接；反之，外部開發者容易串接的 API，內部開發者也不一定容易做到。&lt;/p&gt;
&lt;p&gt;身為開發者，我們當然會希望盡可能設計得讓外部開發者容易使用，並且隱藏內部困難的實作細節。&lt;/p&gt;
&lt;p&gt;其中需要判斷的是「資源」對外界來說概念是否清晰？使用上是否容易？至於內部是否有一個對應的資料表，不需要、也不應該是外部開發者需要考慮的事情。&lt;/p&gt;
&lt;p&gt;再來就是 REST 最常被人們忽略的特性就是 HATEOAS (Hypermedia as the Engine of Application State，超媒體即應用狀態引擎)。&lt;/p&gt;
&lt;p&gt;根據 Roy Fielding (提出 REST API 概念的那位) 在 2014 年的訪談中有提到：&lt;/p&gt;
&lt;p&gt;「HATEOAS 並不是個選項，而是必須實現的約束，否則就不是在做 REST。」&lt;/p&gt;
&lt;p&gt;換言之，對他而言 HATEOAS 不只是非常重要，而是必要的要求。&lt;/p&gt;
&lt;p&gt;REST 背後的其中一個重要動機是它可以在不需要事先知道 URI 的情況下操作瀏覽資源，回傳的資源都要包含其關連資源的資訊。&lt;/p&gt;
&lt;p&gt;白話就是回傳值必須要告訴你：「現在的狀態是什麼？」和「接下來可以幹什麼？」&lt;/p&gt;
&lt;p&gt;當你拿到一個初始的 URI，你就可以對這個系統做任何操作，每一個回傳值都會告訴你有哪些資源可以用，從這個資源到另一個資源，其實這就是一個超媒體(Hypermedia) 的概念，把一個個資源都鏈接起來。&lt;/p&gt;
&lt;p&gt;舉一個例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// GET /apis/hens/1&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;母雞一號&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;_links&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;self&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;href&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://localhost:8000/apis/hens/1&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;eggs&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;href&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://localhost:8080/apis/hens/1/eggs&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;回傳的資源裡包含相關連的 API，這樣一來，當你收到這個回傳值後，就可以根據這些連結取得其他的資源 (這個例子就是與這隻母雞關聯的雞蛋)。&lt;/p&gt;
&lt;p&gt;這種做法還有另一個附帶的好處，由於資源會不停的演化改變，透過 HATEOAS 可以減少假設，對客戶端來說，在資料格式相容的情況下，即使連結修改了也可以直接透過回傳值取得修改的連結，不一定需要修改程式，減少服務端和客戶端之間的偶合性。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;這邊可能有些人會好奇，為什麼有些 API，會像上例一樣，透過類似 self 的方式顯示自己的資源連結呢？理由是因為有些情況下，比如在建立新資源的時候，可能當下還不知道自己的 URI 為何，這時就可以透過 self 來取得。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;在 2008 年時，Leonard Richardson 曾提出「成熟度模型」給 REST API 評等級：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;7%&quot; /&gt;
&lt;col width=&quot;93%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;等級 0&lt;/td&gt;
&lt;td&gt;雖然使用 HTTP，但全都只使用同一個 URI，而且所有操作都是 POST。換言之其實就是單純把 HTTP 當作傳輸方式而已。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;等級 1&lt;/td&gt;
&lt;td&gt;針對個別資源建立不同的 URI，也就是引入「資源」的概念。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;等級 2&lt;/td&gt;
&lt;td&gt;使用 HTTP 方法來定義資源上的作業，如 GET 獲取資源，DELETE 刪除資源，並用 HTTP 狀態碼來表示不同的結果。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;等級 3&lt;/td&gt;
&lt;td&gt;符合 HATEOAS 的要求。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;根據 Roy Fielding 的定義，只有等級 3 才算是真正的 REST API，但實話說，我所見的大部分「號稱自己是 REST API」的 API 也就差不多只有等級 2 而已。&lt;/p&gt;
&lt;p&gt;但真有什麼問題嗎？好像也沒有。&lt;/p&gt;
&lt;p&gt;所以我個人覺得也不用這麼在意 HATEOAS 就是了。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;rest-api-2&quot;&gt;
&lt;h2&gt;設計 REST API&lt;/h2&gt;
&lt;p&gt;知道了什麼是 REST API，那麼接下來就是討論如何設計。&lt;/p&gt;
&lt;p&gt;設計的方式我想每個人都有自己的做法，這裡我整理一下我的思路，還有幾個我覺得要特別注意的地方。&lt;/p&gt;
&lt;p&gt;在實際設計之前，首先是要先全局思考 API 需要的所有功能，並且整理出大概會有哪些資源和支援哪些操作，這裡可以同時參考開發者和使用者的意見。&lt;/p&gt;
&lt;p&gt;另外還要設計資料庫，了解大概有哪些資料需要怎樣被儲存和操作。&lt;/p&gt;
&lt;p&gt;設計資料庫的時候，要注意型態、格式要正確、大小要合理、該下的 index 要下等等，不過這個超出本文的範疇，所以就不多提了。&lt;/p&gt;
&lt;p&gt;有了這些資訊後，我們就可以把功能全部化為一系列的 Action，比如說：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;dl class=&quot;first docutils&quot;&gt;
&lt;dt&gt;農夫 (資源)&lt;/dt&gt;
&lt;dd&gt;&lt;ul class=&quot;first last&quot;&gt;
&lt;li&gt;列舉所有農夫&lt;/li&gt;
&lt;li&gt;取得指定的農夫&lt;/li&gt;
&lt;li&gt;修改農夫的資訊&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;&lt;dl class=&quot;first docutils&quot;&gt;
&lt;dt&gt;母雞 (資源)&lt;/dt&gt;
&lt;dd&gt;&lt;ul class=&quot;first last&quot;&gt;
&lt;li&gt;列舉所有雞&lt;/li&gt;
&lt;li&gt;取得指定的一隻雞&lt;/li&gt;
&lt;li&gt;修改雞的資訊&lt;/li&gt;
&lt;li&gt;殺掉&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;&lt;dl class=&quot;first docutils&quot;&gt;
&lt;dt&gt;雞蛋 (資源)&lt;/dt&gt;
&lt;dd&gt;&lt;ul class=&quot;first last&quot;&gt;
&lt;li&gt;找出所有蛋&lt;/li&gt;
&lt;li&gt;取得一顆指定的蛋&lt;/li&gt;
&lt;li&gt;吃掉&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這時可以先決定一些基本的參數，比如說指定一隻母雞，需要母雞的 ID，找出所有蛋可能要能支援 &lt;code&gt;?hen_id=&amp;lt;id&amp;gt;&lt;/code&gt; 篩選指定母雞的蛋等等。&lt;/p&gt;
&lt;p&gt;接下來就是將 action 轉為實際的 Endpoint，如：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;GET /apis/farmers&lt;/li&gt;
&lt;li&gt;GET /apis/farmers/1&lt;/li&gt;
&lt;li&gt;PUT /apis/farmers/1&lt;/li&gt;
&lt;li&gt;DELETE /apis/farmers/1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原則上「資源」應該都要是名詞，然後利用 HTTP 方法決定動作。資源要採用一致的命名慣例，不用完全和別人一樣沒關係，但同一個產品盡可能必須一致。&lt;/p&gt;
&lt;p&gt;在設計 Endpoint 的時候，建議資源名稱使用複數，比如說：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
GET /apis/farmers/1  # 指定的農夫
GET /apis/farmers    # 全部的農夫
&lt;/pre&gt;
&lt;p&gt;之所以不用單數 &lt;code&gt;apis/farmer&lt;/code&gt; 是因為可能會出現歧異，比如說 &lt;code&gt;farmer&lt;/code&gt; 可能代表「全部農夫」，也可能代表「通稱概念上的農夫」，如果這是一個給農夫用的網站，單純的 &lt;code&gt;/api/farmer&lt;/code&gt; 其實也可能解釋為「農夫自己」。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
GET /apis/farmer/1  # 指定的農夫
GET /apis/farmer    # 這裡如果代表全部的農夫很怪
&lt;/pre&gt;
&lt;p&gt;對我來說，這些解釋都有問題，首先對英文使用者而言，如果 &lt;code&gt;/api/farmer&lt;/code&gt; 代表「全部農夫」，使用單數會覺得很怪；但如果代表「通稱的農夫」的話，那又要如何代表「全部農夫」呢？而且其實也不是所有資源都有這種需求；而如果代表「使用者農夫自己」，同樣也不是所有資源都有類似的需求，如果碰上資源是 garbage 豈不是很尷尬？&lt;/p&gt;
&lt;p&gt;所以結論是不如直接全用複數比較實在。&lt;/p&gt;
&lt;p&gt;當然這是我一家之言，但不管怎麼選擇，至少都要做到一致，我認為這是最基本的要求。&lt;/p&gt;
&lt;p&gt;提到 &lt;code&gt;ID&lt;/code&gt; ，最好也要小心使用 Auto Increment 的功能，像是 &lt;code&gt;/farmers/1&lt;/code&gt; 、 &lt;code&gt;/farmers/2&lt;/code&gt; ，雖然這種方式簡單好實作，但攻擊者卻能很輕易地透過腳本猜數字找到其他所有農夫。對於商業競爭者而言，也可以很簡單的透過這個數字來推估你業務的概況，而這對許多公司而言都是非常重要的機密。&lt;/p&gt;
&lt;p&gt;為了避免這個問題，可以考慮用 Universally Unique Identifier (UUID) 或雪花算法(Snowflake) 取代使用一般的數字 ID。&lt;/p&gt;
&lt;p&gt;在設計資源時，可以考慮將有明顯父子關係的資源用不同層級關聯在一起，通常會有不錯的效果。&lt;/p&gt;
&lt;p&gt;比如用 &lt;code&gt;/farmers/5/hens&lt;/code&gt; 來代表農夫 5 的所有母雞就明顯比 &lt;code&gt;/farmer-hens?farmer=5&lt;/code&gt; 還要清楚明瞭。&lt;/p&gt;
&lt;p&gt;但這件事不要做得太過火，把沒有明顯關聯的資源合在一起，或是把層級定得太深，比如說設計 &lt;code&gt;/farmers/1/hens/99/eggs&lt;/code&gt; 可能就不是好的做法。因為這些資源的關聯性在未來有可能會變更，而這種做法限制了彈性。&lt;/p&gt;
&lt;p&gt;有時候一個概念並不是這麼明確，在設計之初不容易判斷是否該當成一個獨立的資源還是某個資源的部分內容，比如剛剛例子的 &lt;code&gt;egg&lt;/code&gt; 可能在某些業務場景會覺得並不是一個資源，而是包含在 &lt;code&gt;hen&lt;/code&gt; 裡的內容。如果猶豫的話，建議可以直接先當成資源看待，未來再考慮多支援直接放進 &lt;code&gt;hens&lt;/code&gt; 內當裡頭的內容。&lt;/p&gt;
&lt;p&gt;基本上，REST API 的一個核心概念就是透過 HTTP 協定來做操作，所以設計上最好也盡可能遵守協定的要求。&lt;/p&gt;
&lt;p&gt;比如說支援 &lt;code&gt;Accept&lt;/code&gt;，用戶要用什麼格式，就回傳什麼格式，如果不支援就回傳 HTTP 狀態碼 &lt;code&gt;415 Unsupported Media Type&lt;/code&gt; ，而回傳的時候，要加上 &lt;code&gt;Content-Type&lt;/code&gt; 表示回傳的格式。&lt;/p&gt;
&lt;p&gt;原則上最好都要支援 JSON，因為這大概是最通用的格式了，基本所有現代程式語言都有支援，而且也方便人類閱讀。&lt;/p&gt;
&lt;p&gt;其他常見的格式：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;17%&quot; /&gt;
&lt;col width=&quot;83%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;application/x-www-form-urlencoded&lt;/td&gt;
&lt;td&gt;內容會類似 foo=something&amp;amp;bar=1&amp;amp;baz=0 ，雖然常見，但我覺得不算是好的方法，雖然大部分的客戶端都可以處理，但讀取有時會有點麻煩，像
bar=1 的 1 可能是代表字串 1，可能是數字 1，也可能是代表 true，難以判斷。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;text/xml&lt;/td&gt;
&lt;td&gt;我覺得也不是好的方法，雖然也很常見，但同樣不太容易判斷型態，因為他把所有東西都當成字串。如果透過 attribute 表示型態也有侷限，因為使用者的實作常會忽略這段內容 (理由是不好實作)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;既然提到 HTTP 協定，這裡就整理一下我們在操作「資源」的時候，可以有哪些 HTTP Method 可以使用：&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;get&quot;&gt;
&lt;h3&gt;GET&lt;/h3&gt;
&lt;p&gt;取得資源。&lt;/p&gt;
&lt;p&gt;最常見的方法，可以取得所需的資源，成功就會回傳 HTTP 狀態碼 &lt;code&gt;200 OK&lt;/code&gt; ，如果資源不存在就會回傳 &lt;code&gt;404 Not Found&lt;/code&gt; 。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;post&quot;&gt;
&lt;h3&gt;POST&lt;/h3&gt;
&lt;p&gt;通常用來建立新資源或是新任務。&lt;/p&gt;
&lt;p&gt;請求內容通常會包含建立新資源所需要資訊，接著服務端便會回傳新資源的 URI 和資源的詳細內容。&lt;/p&gt;
&lt;p&gt;如果確實建立了新資源，會回傳 HTTP 狀態碼 &lt;code&gt;201 Created&lt;/code&gt; ，如果這個要求進行了處理，但未建立新資源，則可選擇回傳狀態碼 &lt;code&gt;200 OK&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;有時建立的新資源沒有可回傳的內容，那麼就可以直接回傳 &lt;code&gt;204 No Content&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;如果用戶端在建立新資源的時候，內容不合法(比如說缺失內容或格式不對等)，可以回傳狀態碼 &lt;code&gt;400 Bad Request&lt;/code&gt; ，並在回傳內容包含關於錯誤的資訊。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;put&quot;&gt;
&lt;h3&gt;PUT&lt;/h3&gt;
&lt;p&gt;會建立資源或更新現有的資源。&lt;/p&gt;
&lt;p&gt;請求內容會包含要建立或更新的資源，若具有此 URI 的資源已經存在，則會取代此資源。否則會建立新的資源 (若伺服器支援此動作)，但多數情況主要都是用來更新資源內容。&lt;/p&gt;
&lt;p&gt;與 POST 相同，如果建立新資源會回傳 &lt;code&gt;201 OK&lt;/code&gt; ，如果更新了現有資源，就會傳回 &lt;code&gt;200  OK&lt;/code&gt; 或 &lt;code&gt;204 No Content&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;在某些情況下可能會無法更新資源，這時可以考慮回傳狀態碼 &lt;code&gt;409 Conflict&lt;/code&gt; ，並且回傳衝突的原因讓用戶端重送，比較常見的情況是上傳的資源比當前的資源還舊的時候發生。或是內容格式不對，回傳 &lt;code&gt;400 Bad Request&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;PUT 有一個重要的特性即是等冪性。若用戶端多次送出相同的 PUT 要求，結果應該永遠保持不變。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;patch&quot;&gt;
&lt;h3&gt;PATCH&lt;/h3&gt;
&lt;p&gt;要求會針對現有的資源執行「部分更新」。&lt;/p&gt;
&lt;p&gt;用戶端會指定資源的 URI。要求本文會指定要套用到資源的「變更」集。 這可能比使用 PUT 更有效率，因為用戶端只會傳送變更，而不是傳送整個資源的內容。&lt;/p&gt;
&lt;p&gt;理論上 PATCH 也可以建立新的資源 (比如說透過指定一組「null」資源的更新)，但實際上我不曾見過。&lt;/p&gt;
&lt;p&gt;我所知使用的方式有兩種，分別是：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;JSON 修補&lt;/li&gt;
&lt;li&gt;JSON 合併修補&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中後者是相對簡單的方式，簡單來說就是直接傳和資源相同格式的內容，但只包含了想更新的欄位。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;price&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;color&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 有時會用 null 代表要刪除該欄位的內容，但這招不一定適合所有情況&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;small&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ... 其餘沒有要更新的欄位就不傳&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;如需 JSON 合併修補程式的確切詳情，請參閱 RFC 7396。&lt;/li&gt;
&lt;li&gt;JSON 合併修補程式的媒體類型為 &lt;code&gt;application/merge-patch+json&lt;/code&gt; 。&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;回傳的內容和 PUT 的情況差不多，但要注意 PATCH 並不保證冪等性。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;delete&quot;&gt;
&lt;h3&gt;DELETE&lt;/h3&gt;
&lt;p&gt;很簡單，就是移除指定的資源。&lt;/p&gt;
&lt;p&gt;通常刪除就會直接回傳狀態碼 &lt;code&gt;204 No Content&lt;/code&gt; 。畢竟都刪除了，自然也不會有內容可以回傳。而如果對應的資源不存在，則會回傳 &lt;code&gt;404 Not Found&lt;/code&gt; 代表不存在該資源。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;http&quot;&gt;
&lt;h3&gt;HTTP 狀態碼&lt;/h3&gt;
&lt;p&gt;剛才提到了很多不同的 HTTP 狀態碼，有的代表成功，有的代表失敗，雖然有很多，但大略可以分類幾類：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;3%&quot; /&gt;
&lt;col width=&quot;97%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;2xx&lt;/td&gt;
&lt;td&gt;代表請求成功，可以再細分成單純的成功 200 OK、成功新增 201 Created 或是成功但沒有內容 204 No Content 等。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3xx&lt;/td&gt;
&lt;td&gt;代表轉址。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4xx&lt;/td&gt;
&lt;td&gt;代表客戶端的錯誤，代表客戶有什麼地方做錯了，比如請求的內容錯了 400 Bad Request、沒有認證 401 Unauthorized 或是沒有權限的 403 Forbidden 等。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5x&lt;/td&gt;
&lt;td&gt;代表服務端的錯誤，如內部服務錯誤 500 Internal Server Error，身為一位後端工程師，理想上最好所有錯誤都是 4xx 而不是 5xx。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;盡量就不要讓失敗只有 400 Bad Request 或是 500  Internal Server Error 這兩種回傳，使用多種不同的狀態碼來區分不同的情況可以讓前端更了解發生了什麼事，以便做出不同的應對。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h3&gt;回傳的內容&lt;/h3&gt;
&lt;p&gt;訂好了 Endpoint 和操作方式，接下來就是決定服務端回傳的內容。&lt;/p&gt;
&lt;p&gt;首先自然是要先考慮安全性的問題，有些敏感資料像是密碼，雖然使用者創建的時候會需要，但是回傳的時候就不應該出現。&lt;/p&gt;
&lt;p&gt;還有就是可讀性，回傳內容一個很重要的要點是需要根據「使用者的需求」來設計。如果目標使用者單純只是對公司內部的人還好說，畢竟可能會有別的不同因素要考量，但如果是會對外開放的 API 就不要忽略這一點。&lt;/p&gt;
&lt;p&gt;畢竟如果用你設計的 API，用戶使用時還得不停的查文件，然後驚呼被騙，體驗就會很差。&lt;/p&gt;
&lt;p&gt;所以不要用大家看不懂的語言、不要用奇怪的型態、不要使用自定義的縮寫、不要用自以為是的「常識」來假設用戶，這樣都可以減少用戶必須查文件的需求。&lt;/p&gt;
&lt;p&gt;比如說型態和內容要符合使用者的預期，有時最讓人不爽的不是看不懂，而是讓使用者以為自己看得懂，但結果卻不符合預期的情況。明明欄位是「message」，但卻回傳一個數字；或欄位是「status」卻回傳 1。請問誰知道 1 代表什麼意思？是 0 代表成功還是 1 代表成功？&lt;/p&gt;
&lt;p&gt;除了內容本身以外，考慮網速，我們還得盡可能的減少請求(Request) 的數量，但又不能一次讓使用者下載太多資料造成延遲，這兩者之間必須要取得平衡。&lt;/p&gt;
&lt;p&gt;如果一次回應必要資訊給得不足，使用者就得被迫多打幾次請求來拿取必要資訊，造成使用者體感上的延遲和前端開發的麻煩。&lt;/p&gt;
&lt;p&gt;那如果一次給完所有資訊呢？&lt;/p&gt;
&lt;p&gt;也不見得是好事。&lt;/p&gt;
&lt;p&gt;因為雖然對前端開發者來說，一次拿好資料，之後就不用再拿，開發上會比較簡單。但對真正的使用者而言，一次給太大包的資料可能會增加初次顯示的延遲，造成使用者覺得網站很慢的觀感。&lt;/p&gt;
&lt;p&gt;而有可能大部分的資料可能不是使用者第一眼就需要看到的，可以用骨架屏顯示大致的框架和部分的內容，再依次顯示其餘的內容，雖然整體其實並不會比較快，但卻有更好的使用者體驗。&lt;/p&gt;
&lt;p&gt;這邊提供兩個小技巧：&lt;/p&gt;
&lt;p&gt;首先是同樣的資源不用重覆給多次。&lt;/p&gt;
&lt;p&gt;如果評論和作者都相同，不需要給每一則評論都給一次作者資料。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 每則評論都會有對應的作者，但有可能這些評論都是同一個作者&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;頭香&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;兩大類&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;一樓有病&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 可以把評論和作者拆開來，變成這樣&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;頭香&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;一樓有病&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 拆出來，或是直接拆成兩個資源分別請求&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;兩大類&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;另一個技巧就是讓使用者自行決定內容的詳細程度。有時會發生一種情況，那就是 A 畫面需要精簡的資料，而 B 畫面需要比較詳細的資料，所以 API 為了能同時支援 A、B 兩個畫面，就會直接給 B 畫面所需的所有資訊，但其實對於 A 畫面來說，這些多餘的資訊是不必要的。&lt;/p&gt;
&lt;p&gt;但其實這件事我們可以給使用者選擇，比如說 A 畫面只需要評論內容：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// GET /comments&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;頭香&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;一樓有病&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;B 畫面除了評論內容還需要作者資訊，可以用 query string 的方式指定：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// GET /comments?embed=author&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;頭香&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;兩大類&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;一樓有病&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這樣 A 畫面就不會拿到不需要的資訊。&lt;/p&gt;
&lt;p&gt;至於 API 具體回傳的格式，如果公司內部本來就有規範，那自然就繼續延用。但如果沒有的話，我推薦可以參考通用標準的規範，比如說 &lt;a class=&quot;reference external&quot; href=&quot;https://jsonapi.org&quot;&gt;JSON:API&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;一方面是溝通方便，如果開發者原本就知道這個規範就可以省去學習的成本，而且這類規範除了一些特別極端的例子，幾乎已經考慮到了所有的情況，通常應該會比少數幾個人，在趕工壓力下一拍腦袋想出來的格式還要全面許多。&lt;/p&gt;
&lt;p&gt;這些舉一個 JSON:API 官網的例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;links&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;self&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://example.com/articles&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;next&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://example.com/articles?page[offset]=2&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;last&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://example.com/articles?page[offset]=10&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;articles&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;attributes&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;JSON:API paints my bikeshed!&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;relationships&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;links&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;self&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://example.com/articles/1/relationships/author&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;related&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://example.com/articles/1/author&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;people&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;9&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;links&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;self&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://example.com/articles/1/relationships/comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;related&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://example.com/articles/1/comments&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;5&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;12&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;links&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;self&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://example.com/articles/1&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;included&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;people&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;9&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;attributes&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;firstName&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;Dan&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;lastName&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;Gebhardt&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;twitter&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;dgeb&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;links&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;self&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://example.com/people/9&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;5&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;attributes&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;body&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;First!&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;relationships&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;people&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;links&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;self&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://example.com/comments/5&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;12&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;attributes&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;body&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;I like XML better&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;relationships&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;people&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;9&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;links&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;self&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;http://example.com/comments/12&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看這個例子，我想通常大部分的人需要思考的反而不是缺了什麼，而是不用什麼，所以是一份非常實用的參考資料。&lt;/p&gt;
&lt;p&gt;當然 API 並不只是定好 Endpoint 和功能就行了，還有一些重要的議題必須考慮。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h2&gt;重要議題&lt;/h2&gt;
&lt;div class=&quot;section&quot; id=&quot;authentication&quot;&gt;
&lt;h3&gt;授權 (Authentication)&lt;/h3&gt;
&lt;p&gt;首先，你怎麼知道使用你 API 的人是目標使用者而不是攻擊者呢？&lt;/p&gt;
&lt;p&gt;在設計 API 的時候，這幾乎是不可避免必須要討論的東西。&lt;/p&gt;
&lt;p&gt;當然了，不同的使用情境會有不同的需求，像是如果只有提供一些唯讀而且沒有敏感的資料也許就不用管這件事，又或是某些公司內部 API 可能也不需要這麼做。&lt;/p&gt;
&lt;p&gt;(但也不好說，畢竟如果不小心讓駭客進了內網，那麼問題就大條了)&lt;/p&gt;
&lt;p&gt;但如果授權確實是考量的話，最簡單的方式就是 &lt;code&gt;Basic Authentication&lt;/code&gt; ，這是最基本的模式，不需要 cookies、session 甚至也不用自行實作網站登入頁面，瀏覽器會自動跳出對話框讓使用者填帳密。&lt;/p&gt;
&lt;p&gt;但缺點是非常不安全，其原理就是將帳密用 base64 編碼後放進 HTTP 的 Header 傳給服務端，服務端再以此來確認身份。&lt;/p&gt;
&lt;p&gt;但由於 base64 是可以輕易反編碼的，所以一旦被人攔截到封包，你的帳密就直接被人看光光了。&lt;/p&gt;
&lt;p&gt;雖然如果網站使用 HTTPS 可以避免這個問題，至少別人無法輕易攔截封包查看裡面的內容，但兩端仍然藏不住。&lt;/p&gt;
&lt;p&gt;身為客戶端的瀏覽器會把帳密存起來，如果有人能碰到這台電腦就有可能拿到。&lt;/p&gt;
&lt;p&gt;服務端也是如此，使用者可能也不想讓網站維護者實際拿到自己的密碼，畢竟不少人會用同樣的帳密在不同的網站上。如果心存不良的網站持有者（或是能登上那台機器的員工）可能會藉此登入你其他網站，取得機密資訊。&lt;/p&gt;
&lt;p&gt;所以通常我們會使用 hash 的方式在傳上服務端之前便用 hash 加密，服務端也只儲存 hash 後的結果做比對。這樣一來使用者就不需要真正上傳密碼給服務端，而服務端又能驗證使用者。&lt;/p&gt;
&lt;p&gt;大概念是這樣，但其實還有很多細節，比如說如果單純用密碼 hash，那麼常見的密碼仍會被別人猜到，所以還需要加上「鹽」才行；而因為可能會被 Replay Attack，可能還得加上時間資訊，才不會被人透過重送同樣的封包破解；甚至 Hash 函數本身如果是用 MD5 也不成，因為 MD5 屬於已經被破解的 Hash 函式，所以必須用其他的代替等等，因為不是本文主題，所以這裡就不多提。&lt;/p&gt;
&lt;p&gt;總之，因為要考量的點非常多，做得不好反而不安全，所以通常不會自己做，而是用一些成熟的框架解決。&lt;/p&gt;
&lt;p&gt;其他還有第三方登入的方式，像 OAuth 等不同的方法，根據自己的業務需要來決定方案。如果你是不知名的廠商，如果不是用第三方登入，使用者可能會直接放棄使用你的網站；如果你的網站足夠大，這種做法可能反而會有反效果，所以還是得視情況而定。&lt;/p&gt;
&lt;p&gt;另外還要注意一點，HTTP 的標準提供了一個方式可以傳遞這類密鑰的加密資訊，也就是放在 Header 的 Authorization 中，盡量不要自作聰明放在其它地方，因為其他地方有不同的用途，可能會不利於安全性。&lt;/p&gt;
&lt;p&gt;比如說如果直接把密鑰放進 query string 傳遞，就有可能會被存進 log 或是瀏覽器的瀏覽紀錄中，可能就不是好的選擇。&lt;/p&gt;
&lt;p&gt;但事情沒有絕對，經過合理的設計，配合一些 sign 的機制，我也是有見過放在 query string 的。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;pagination&quot;&gt;
&lt;h3&gt;分頁 (Pagination)&lt;/h3&gt;
&lt;p&gt;要減少使用者下載的延遲，「分頁」通常是一個非常重要的工具，它考慮的是－－如何用合理的方式將 Data 分成多個 HTTP request，讓回傳時的 Response 大小不會過大。&lt;/p&gt;
&lt;p&gt;分頁可以減少使用者下載的時間，而且如果不這麼做，以後端角度來說一次可能會抓太多資料，資料庫會不開心！&lt;/p&gt;
&lt;p&gt;一個好的有做分頁的 API 應該要能讓使用者選擇「要一次回傳多一個 items」，比如說 &lt;code&gt;/comments?number=12&lt;/code&gt; ，但是別忘了要設定數量的上限，不然資料庫還是會不開心。&lt;/p&gt;
&lt;p&gt;至於怎麼設計分頁的系統，則非常看使用情境，我看過最常見也是最基本的方式，就是直接讓使用者指定頁數的大小和頁碼，然後回傳當前的頁碼、單頁總數、總頁數等資訊。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// GET /comments?pageIndex=1&amp;amp;pageSize=20&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;pagination&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;pageIndex&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;pageSize&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;totalSize&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;30&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這個方法雖然回傳的資訊很充足，但要取得所需的資訊其實很花資料庫的運算效能，雖然理論上可以用快取，但一個 API 通常會提供多個條件供使用者修改，資料本身也可能很常變動。不一定有辦法對這些狀態都設快取。&lt;/p&gt;
&lt;p&gt;而且這種回傳方法可能會有潛在的問題，如果中間突然新增或刪除一個新的 item，使用者在拿取不同頁的時候可能會重覆或是少出現的情況。&lt;/p&gt;
&lt;p&gt;另一種設計方式是使用 Cursor，概念上就是類似資料庫使用 offset 的方式拿資料， 因為不是指定「第幾筆資料」而是要「某個位置之後的資料」，所以效能通常會好很多。但這種方式需要限定業務場景才能使用。&lt;/p&gt;
&lt;p&gt;順帶一提，在拿列表的時候如果沒有資料，不建議用 404 Not Found，因為 URL 的指向其實不是錯的，只是沒有資料而已，列表不會不存在，只是空的而已。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-3&quot;&gt;
&lt;h3&gt;錯誤處理&lt;/h3&gt;
&lt;p&gt;在設計 API 的時候，錯誤處理絕對是非常重要的一環。其中最基本的方式就是直接用 HTTP 的狀態碼來表示。&lt;/p&gt;
&lt;p&gt;前面有說到 HTTP 定義了非常多狀態碼可以代表失敗的情況，但畢竟是通用規則，顯然不可能滿足所有的業務需求，所以有些人會自行定義更多狀態碼，反正 400 到 499 還有很多空的狀態碼沒有用到。&lt;/p&gt;
&lt;p&gt;但我覺得這個做法很奇怪，因為大部分的錯誤都是業務上定義的錯誤，我們不太可能直接用狀態碼表示。&lt;/p&gt;
&lt;p&gt;所以到頭來，我們還是得另外定義一個錯誤碼來表示我們業務上的各種不同錯誤狀態。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;code&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;400001&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 另外定義錯誤碼&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;reason&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞飛出大氣層啦！&amp;quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;既然如此，似乎就沒有必要另外延伸定義新的狀態碼。HTTP 的狀態碼應該只用來定位大略的問題，而真正的錯誤則由裡頭的錯誤碼決定才對。&lt;/p&gt;
&lt;p&gt;更進一步來說，即使是標準，不常見的狀態碼可能也沒必要使用，直接整合成常見的幾個即可。&lt;/p&gt;
&lt;p&gt;畢竟真正的錯誤是用自定義的錯誤碼來判斷，那就沒必要用一些奇怪少見的狀態碼來造成前端開發者的困擾。&lt;/p&gt;
&lt;p&gt;至於要怎麼做，我覺得還是看公司，還是前面的老話，反正大部分公司的 API 通常都也只有自己公司內部會使用，只要能符合自身業務需求，怎麼定都沒差，能用就成。&lt;/p&gt;
&lt;p&gt;不過說是這樣說，也不要所有回傳的狀態碼都全部是 200，也不要不管客戶端錯或是服務端錯誤就全部回傳 500。&lt;/p&gt;
&lt;p&gt;理由是因為前端所使用的函式庫很有可能會針對這2xx、4xx 和 5xx 的狀態碼有不同的處理，如果把應該是 4xx 的狀態碼給成 500 反而可能會造成前端開發人員的困擾。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;versioning&quot;&gt;
&lt;h3&gt;版本 (Versioning)&lt;/h3&gt;
&lt;p&gt;事情並不是做完就結束，業務會不停地變動，需求會不斷地來，假如不來，那表示你就沒事可幹，那麼老闆就會把你幹掉，所以你最好祈禱事情永遠做不完……&lt;/p&gt;
&lt;p&gt;所以說 API 鐵定是會持續更新的。&lt;/p&gt;
&lt;p&gt;(如果你還沒被老闆幹掉的話)&lt;/p&gt;
&lt;p&gt;但是對外開放的 API，也不能說改就改，畢竟你也不可能要求客戶做到即時更新，所以這時服務端就必須同時提供新舊多個版本的 API 才行。&lt;/p&gt;
&lt;p&gt;至於怎麼做？方法有很多，一個常見的做法就是直接將版號放在 URI 裡，如：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
http[s]://api.marco79423.net/v1/hens
http[s]://api.marco79423.net/v2/hens
&lt;/pre&gt;
&lt;p&gt;這個方法的好處是非常好管理，直接改 v1 和 v2 即可，對服務端和客戶端都相當簡單，所以通常是第一個採用的做法。&lt;/p&gt;
&lt;p&gt;至於缺點的話，概念上不太 RESTful，「資源」應該比較像是永久連結，理論上不應該可以修改，如果說 Internet 是藉由連結互相連結而產生的，改來改去就會爆炸。&lt;/p&gt;
&lt;p&gt;以此例來說，明明「雞(hen)」這個資源就是同一個概念，卻用不同的 URI，會讓人覺得「難道 v1 版的雞(hen) 和 v2 版的雞 (hen) 有什麼本質的不同嗎？&lt;/p&gt;
&lt;p&gt;不過我個人覺得這比較像是在挑毛病，並不是什麼大的問題，所以有名的案例有很多，像是 Disqus、Tumblr、Twitter、Youtube 等都是這麼做的。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;Disqus Web API： &lt;a class=&quot;reference external&quot; href=&quot;https://disqus.com/api/docs/&quot;&gt;https://disqus.com/api/docs/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tumblr API: &lt;a class=&quot;reference external&quot; href=&quot;https://www.tumblr.com/docs/en/api/v2&quot;&gt;https://www.tumblr.com/docs/en/api/v2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a class=&quot;reference external&quot; href=&quot;https://developer.twitter.com/en/docs/api-reference-index&quot;&gt;https://developer.twitter.com/en/docs/api-reference-index&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Youtube: &lt;a class=&quot;reference external&quot; href=&quot;https://developers.google.com/youtube/v3&quot;&gt;https://developers.google.com/youtube/v3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;另外似乎也有人用 host 區分：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
http[s]://api-v1.marco79423.net/hens/1
http[s]://api-v2.marco79423.net/hens/2
&lt;/pre&gt;
&lt;p&gt;這個方法理論上很簡單，幾乎有剛才的方法的所有好處，而且分不同 Server 很容易，甚至可以輕易做到 v1 和 v2 用完全不同程式碼實現。&lt;/p&gt;
&lt;p&gt;但實務上，管理域名的和實作 API 的時常是不同的部門，遠不如實作者自行控制(不同版本直接改路由)比較容易。&lt;/p&gt;
&lt;p&gt;而且這個方法同樣也有前者的缺點，因為不同版本的資源還是用不同的 URI。&lt;/p&gt;
&lt;p&gt;如果不想改路由，另一個可能的方案就是把版本資訊放在 body 裡，如：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
POST /apis/hens HTTP/1.1
Host: marco79423.net
Content-Type: application/json

{
    &amp;quot;version&amp;quot;: &amp;quot;1.0&amp;quot;
}
&lt;/pre&gt;
&lt;p&gt;這個方案好處是路由是一致的，但缺點是不同的 &lt;code&gt;Content-Type&lt;/code&gt; 會有不同的回傳方式，如果碰到 JSON 或是 XML 好說，但碰到 CSV 或是 JPG/PNG 這類的格式就麻煩了。&lt;/p&gt;
&lt;p&gt;如果是放 query string 的話，也等於是在改路由，那倒不如用第一種方法比較方便。&lt;/p&gt;
&lt;p&gt;剩下的選擇就是放 Header 了，比如說：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
GET /apis/hens HTTP/1.1
Host: marco79423.net
APIVersion: 1.0
&lt;/pre&gt;
&lt;p&gt;但要注意使用自定義的 Header 可能會有 Cache 問題，所以回傳必須要加上 Vary 才能正確運作，類似這樣：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
HTTP/1.1 200 OK
APIVersion: 1.0
Vary: APIVersion
&lt;/pre&gt;
&lt;p&gt;不過這種方式使用者可能會不容易注意到版本的變化，畢竟不會有多少人會檢查回傳回來的 Header。而且自定義 Header 就等於要求使用者必須要看文檔才會知道，畢竟有可能是 &lt;code&gt;API-Version&lt;/code&gt; 也有可能是 &lt;code&gt;X-Api-Version&lt;/code&gt; ，如果不看文檔，誰會猜得出是哪一種啊？&lt;/p&gt;
&lt;p&gt;但你說有沒有著名案例呢？還真的有，那就是很愛搞自定義規則的微軟的 Azure。&lt;/p&gt;
&lt;p&gt;（怎麼感覺好像一點都不意外？）&lt;/p&gt;
&lt;p&gt;話說回來，既然都可以接受放 Header 了，為什麼不直接用 Content Negotiation 的方式判斷版本呢？畢竟 &lt;cite&gt;Accept&lt;/cite&gt; 本來就是設計用來指定資源的不同格式，所以用來指定版本感覺也很合理？&lt;/p&gt;
&lt;p&gt;所以 github 就是這麼做的：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
# 格式：application/vnd.github[.version].param[+json]
Accept: application/vnd.github+json
Accept: application/vnd.github.v3+json
&lt;/pre&gt;
&lt;p&gt;這樣的好處是放在 &lt;cite&gt;Accept&lt;/cite&gt; 不會有快取的問題，同時也不會有 URL 不一致的狀況，最重要的是非常 RESTful。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;Github Docs: &lt;a class=&quot;reference external&quot; href=&quot;https://docs.github.com/en/rest/overview/media-types&quot;&gt;https://docs.github.com/en/rest/overview/media-types&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;另外 Facebook 還有一種比較特別的做法，叫做「 Feature Flagging」，那就是每個 App 都可以設定自選的版本。&lt;/p&gt;
&lt;p&gt;如果 API 有更新，就會主動傳訊息給開發者哪些 APP 使用到的 API 更新了，要求使用者調整。&lt;/p&gt;
&lt;p&gt;如果改動不影響開發者，開發者就可以選擇 Enable，如果會影響，就可以先暫停。&lt;/p&gt;
&lt;p&gt;⋯⋯但幾個月以後還是會強迫更新。&lt;/p&gt;
&lt;p&gt;這個方法的好處是官方可以不用一直支援舊版的 API，只需要維護一份和一小部分新的 API 而已，而對使用者來說，如果改的是與 App 無關的功能也不用擔心出問題。&lt;/p&gt;
&lt;p&gt;壞處是當使用者轉換 API 的過程中，可能會有一瞬間不能用的情況發生，因為你不能先放 new code 上去。結果為了解決這個問題，可能要在同時寫支援兩個版本才行。&lt;/p&gt;
&lt;p&gt;更大的壞處是如果你不是在 facebook 這種超強的大公司，大概很難強迫開發者這麼做。&lt;/p&gt;
&lt;p&gt;雖然前面說了這麼多方法，但也許最好的方法是直接詢問你的目標使用者他們想要的是什麼？畢竟每間公司的業務場景可能都不太一樣，不管什麼方法，如果目標使用者覺得不好用就沒意義了。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-4&quot;&gt;
&lt;h2&gt;後記&lt;/h2&gt;
&lt;p&gt;洋洋灑灑的寫了一大堆，是我在這些年寫的一些筆記和心得。&lt;/p&gt;
&lt;p&gt;而我也不知道所有事，我所能做的也只是盡我所能分享我所見的、所知的使用心得，因此不必然我說的就是比較好，雖然可以拿來當參考，但也僅此而已，不必奉為圭臬。&lt;/p&gt;
&lt;p&gt;雖然其實還是很多沒寫，像是原本想寫非同步任務、傳大型檔案、服務端主動推送、API 文件之類的，但再多下去估計就沒人看了，所以就先這樣吧，如果真有人有興趣，我再回頭為這篇文章加料，讓這篇文章長到天荒地老，喔喔喔喔喔喔～～～&lt;/p&gt;
&lt;p&gt;以上。&lt;/p&gt;
&lt;p&gt;⋯⋯雖然估計現在就已經長到不會有人想看就是了。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-5&quot;&gt;
&lt;h2&gt;參考資料&lt;/h2&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.amazon.com/Build-APIs-You-Wont-Hate/dp/0692232699&quot;&gt;Build APIs You Won&#39;t Hate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://carsonwah.github.io/http-authentication.html&quot;&gt;開發者必備知識 - HTTP認證（HTTP Authentication）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://zh.wikipedia.org/zh-tw/%E8%A1%A8%E7%8E%B0%E5%B1%82%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2&quot;&gt;表現層狀態轉換&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://docs.microsoft.com/zh-tw/azure/architecture/best-practices/api-design&quot;&gt;RESTful web API 設計&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;其實有一堆參考資料因為年代久違遺失了，我感到很抱歉……&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Jessiclient]]></title>
        <id>https://blog.marco79423.net/articles/jessiclient/</id>
        <link href="https://blog.marco79423.net/articles/jessiclient/"/>
        <updated>2021-05-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[
網站連結： https://jessiclient.marco79423.net
前陣子在開發公司的專案時，因為會用到 Websocket，所以花了不少時間在測試。
認真覺得網路上的工具都莫名的難用 (醜)，明明 Websocket 應該也不算是很罕見的工具，為啥找不到自己喜歡的呢 ...]]></summary>
        <content type="html"><![CDATA[&lt;img alt=&quot;https://blog.marco79423.net/backend/static/26bd8196-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/26bd8196-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;網站連結： &lt;a class=&quot;reference external&quot; href=&quot;https://jessiclient.marco79423.net&quot;&gt;https://jessiclient.marco79423.net&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;前陣子在開發公司的專案時，因為會用到 Websocket，所以花了不少時間在測試。&lt;/p&gt;
&lt;p&gt;認真覺得網路上的工具都莫名的難用 (醜)，明明 Websocket 應該也不算是很罕見的工具，為啥找不到自己喜歡的呢？&lt;/p&gt;
&lt;p&gt;總之因為這個原因，所以後來我決定自己搞一個可以在線上方便測試 Websocket 效果的工具。&lt;/p&gt;
&lt;p&gt;之前一直都沒有機會真的好好使用過 Websocket，所以剛好可以拿來學習 Websocket 的相關技術，就當作一個可以動的學習筆記吧！&lt;/p&gt;
&lt;p&gt;雖然自認自己前端寫得不見得比後端少，但畢竟是後端工程師，所以很多時候前端在想什麼不見得知道，久沒碰可能就脫節了，所以用這個來練練手，前端有什麼問題可以方便解決。&lt;/p&gt;
&lt;p&gt;但既然要幹，那就要好好幹，所以我打算好好做，定時更新功能。如果有什麼需求也可以留言給我。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;更新紀錄&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;&lt;dl class=&quot;first docutils&quot;&gt;
&lt;dt&gt;1.1.0 版 - 2021-06-15&lt;/dt&gt;
&lt;dd&gt;&lt;ul class=&quot;first last&quot;&gt;
&lt;li&gt;調整部分訊息內容&lt;/li&gt;
&lt;li&gt;新增成功送出請求的通知&lt;/li&gt;
&lt;li&gt;新增自選語系的功能&lt;/li&gt;
&lt;li&gt;新增新增常用請求的對話框&lt;/li&gt;
&lt;li&gt;修 bug&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;&lt;dl class=&quot;first docutils&quot;&gt;
&lt;dt&gt;1.0.0 版 - 2021-05-04&lt;/dt&gt;
&lt;dd&gt;&lt;ul class=&quot;first last&quot;&gt;
&lt;li&gt;發布 1.0.0&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[mat 代理伺服器]]></title>
        <id>https://blog.marco79423.net/articles/mat-%E4%BB%A3%E7%90%86%E4%BC%BA%E6%9C%8D%E5%99%A8/</id>
        <link href="https://blog.marco79423.net/articles/mat-%E4%BB%A3%E7%90%86%E4%BC%BA%E6%9C%8D%E5%99%A8/"/>
        <updated>2021-03-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[
Github：https://github.com/marco79423/mat-server
後端開發用代理伺服器，能攔截設定的 API，直接回傳指定的結果，其餘則直接轉發實際伺服器的回傳值。

客戶端 -&amp;gt; mat server -&amp;gt; 實際的伺服器 ...]]></summary>
        <content type="html"><![CDATA[&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2274ff92-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2274ff92-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;Github：&lt;a class=&quot;reference external&quot; href=&quot;https://github.com/marco79423/mat-server&quot;&gt;https://github.com/marco79423/mat-server&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;後端開發用代理伺服器，能攔截設定的 API，直接回傳指定的結果，其餘則直接轉發實際伺服器的回傳值。&lt;/p&gt;
&lt;blockquote&gt;
客戶端 -&amp;gt; mat server -&amp;gt; 實際的伺服器&lt;/blockquote&gt;
&lt;p&gt;在開發新功能的時候，時常後端功能尚未開發完成，前端必須等待後端功能完成後才能開發的情況，透過 mat 代理伺服器可以直接設定好預計的回傳內容，讓前端不需要通靈開發。&lt;/p&gt;
&lt;p&gt;適合小專案快速開發，可以直接架在後端的電腦上，讓前端連進來，隨時可以修改 API 格式。&lt;/p&gt;
&lt;p&gt;特色：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;隨時可以修改設定，自動更新回傳值內容&lt;/li&gt;
&lt;li&gt;支援 query string&lt;/li&gt;
&lt;li&gt;支援取代函式&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h2&gt;安裝&lt;/h2&gt;
&lt;div class=&quot;section&quot; id=&quot;pip&quot;&gt;
&lt;h3&gt;透過 pip 安裝&lt;/h3&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; pip install mat-server
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h2&gt;使用方法&lt;/h2&gt;
&lt;div class=&quot;section&quot; id=&quot;section-3&quot;&gt;
&lt;h3&gt;命令列操作&lt;/h3&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 初始化 mat (產生 mat-data 設定資料夾)&lt;/span&gt;
mat&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;init

&lt;span class=&quot;c1&quot;&gt;# 啟動伺服器 (&amp;lt;http://0.0.0.0:3000&amp;gt;)&lt;/span&gt;
mat&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;-p&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;m&quot;&gt;3000&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-4&quot;&gt;
&lt;h3&gt;設定檔相關&lt;/h3&gt;
&lt;p&gt;產生的 mat-data 資料夾架構如下：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
mat-data/
    config.yml               # 設定要代理的伺服器等設定
    data/
        hello.json
&lt;/pre&gt;
&lt;p&gt;mat-server 會直接使用當前目錄下的 mat-data/config.yml 設定檔，只有這個是固定的，其他的回傳值內容都可以自由設定，比如說只要指定好路徑， data/hello.json 其實也可以放在 mat-data 外面。&lt;/p&gt;
&lt;p&gt;其中 config.yml 的格式如下：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;proxy_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;http://target_server&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 要代理的伺服器&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;listen_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;hello/name&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;     &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 要攔截的路由  (http://target_server/hello)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;GET&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                   &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# HTTP Method  (預設為 GET)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;status_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;              &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 回傳的 status code (預設為 200 OK)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                     &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 回傳值設定&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;hello world&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 回傳 {&amp;quot;msg&amp;quot;: &amp;quot;hello world&amp;quot;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;listen_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;hello&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 設定 query string (http://target_server/hello?name=marco&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;marco&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;file_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;data/hello.json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 回傳 hello.json 的檔案內容&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;範例： 直接透過 config.yml 設定路由和回傳值&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;proxy_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;&amp;lt;https://blog.marco79423.net&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;listen_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;backend/api/articles/&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;Hello mat-server&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;歡迎使用 mat-server&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-5&quot;&gt;
&lt;h2&gt;進階用法&lt;/h2&gt;
&lt;div class=&quot;section&quot; id=&quot;listen-path&quot;&gt;
&lt;h3&gt;參數化 listen_path&lt;/h3&gt;
&lt;p&gt;listen_path 是使用正規表達式 (regex) 判斷是否符合，所以碰到需要無視參數內容時可以使用。&lt;/p&gt;
&lt;p&gt;範例： 使用 regex 設定 listen_path&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;proxy_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;&amp;lt;https://blog.marco79423.net&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;listen_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;backend/api/articles/\\\\\\\\d+&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;Hello mat-server&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;歡迎使用 mat-server&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-6&quot;&gt;
&lt;h3&gt;取代函式&lt;/h3&gt;
&lt;p&gt;可以使用 replace_funcs 取代值。&lt;/p&gt;
&lt;p&gt;目前可以支援的 replace_funcs：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;32%&quot; /&gt;
&lt;col width=&quot;24%&quot; /&gt;
&lt;col width=&quot;45%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;函式&lt;/td&gt;
&lt;td&gt;名稱&lt;/td&gt;
&lt;td&gt;說明&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;uuid_v4&lt;/td&gt;
&lt;td&gt;UUID v4&lt;/td&gt;
&lt;td&gt;用 UUID v4 取代&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;random_int&lt;/td&gt;
&lt;td&gt;亂數&lt;/td&gt;
&lt;td&gt;用亂數整數取代&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;範例： 使用函式函式設定 config.yml&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;proxy_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;&amp;lt;https://blog.marco79423.net&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;listen_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;backend/api/articles/&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;replace_funcs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;uuid_v4&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                          &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 看見 {uuid_v4()} 就會取代為 UUID v4 回傳&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;random_int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                       &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 看見 {random_int(start, end) 就會用亂數取代&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;requestID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uuid_v4()&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;Hello mat-server&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;l l-Scalar l-Scalar-Plain&quot;&gt;歡迎使用 mat-server (目前在線人數 {random_int(100, 1000)})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-7&quot;&gt;
&lt;h3&gt;當作函式庫使用&lt;/h3&gt;
&lt;p&gt;mat 內部使用 FastAPI 當作 Server。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;fastapi&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;uvicorn&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;mat_server&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fastapi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FastAPI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 實作自己的路由&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;/hello&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;hello&amp;#39;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 取得 mat_server 所使用的 API Router&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mat_server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;api_router&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_server_api_router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;include_router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 啟動服務器&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;uvicorn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;main:app&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;0.0.0.0&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reload&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-8&quot;&gt;
&lt;h3&gt;特殊路由&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;GET /_mat&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;回傳設定檔的內容&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;server&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;proxy_url&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;lt;https://blog.marco79423.net&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;routes&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;listen_path&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;backend/api/articles/&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;response&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;Hello mat-server&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;歡迎使用 mat-server&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;fao&quot;&gt;
&lt;h2&gt;FAO&lt;/h2&gt;
&lt;div class=&quot;section&quot; id=&quot;content-type&quot;&gt;
&lt;h3&gt;決定回傳 Content-Type 的策略？&lt;/h3&gt;
&lt;p&gt;如果是回傳 mock 資料，會根據設定檔的設定決定 Content-Type，使用 file_path 或是用 data 會有不同的策略。&lt;/p&gt;
&lt;p&gt;使用 data 的情況：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;如果 data 是字串，就會直接當成網頁型態 (text/html; charset=utf-8)&lt;/li&gt;
&lt;li&gt;除此之外都當作 JSON 型態 (application/json)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用 file_path 的情況：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;會根據副檔名來猜測型態，比如說 xxx.jpg 就會是 image/jpeg&lt;/li&gt;
&lt;li&gt;如果沒有副檔名或是猜不到，就會當成網頁型態 text/html; charset=utf-8&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-9&quot;&gt;
&lt;h2&gt;實作相關&lt;/h2&gt;
&lt;div class=&quot;section&quot; id=&quot;section-10&quot;&gt;
&lt;h3&gt;專案架構&lt;/h3&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
setup.py
requirements.txt
mat_server/
    __init__.py                             # 函試庫進入點
    __main__.py                             # 主程式進入點
    app/                                    # 應用層
        container.py                        # DI container
        cli.py                              # 命令列設定
        manager.py                          # 功能管理
        server.py                           # 服務器設定
            ...
    domain/                                 # 領域層
        use_cases/                          # 領域的用例
            ...
        entities/                           # 領域的實例
            ...
        repositories/                       # 領域的儲存庫
            ...
        helpers/                            # 工具包
            ...
    infrastrcture/                          # 實作層
        repositories/
            ...
        helpers/
            ...
    data/                                   # 放置靜態檔案 (如預設設定檔)
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[傑西神教 jessigod]]></title>
        <id>https://blog.marco79423.net/articles/%E5%82%91%E8%A5%BF%E7%A5%9E%E6%95%99-jessigod/</id>
        <link href="https://blog.marco79423.net/articles/%E5%82%91%E8%A5%BF%E7%A5%9E%E6%95%99-jessigod/"/>
        <updated>2021-03-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[
網站： https://jessigod.marco79423.net/
Github： https://github.com/marco79423/jessigod

實作相關

前端
使用 next ...]]></summary>
        <content type="html"><![CDATA[&lt;img alt=&quot;https://blog.marco79423.net/backend/static/247f5c2e-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/247f5c2e-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;網站： &lt;a class=&quot;reference external&quot; href=&quot;https://jessigod.marco79423.net/&quot;&gt;https://jessigod.marco79423.net/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Github： &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/marco79423/jessigod&quot;&gt;https://github.com/marco79423/jessigod&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h2&gt;實作相關&lt;/h2&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h3&gt;前端&lt;/h3&gt;
&lt;p&gt;使用 next.js / Material-UI 開發。&lt;/p&gt;
&lt;p&gt;專案架構:&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
frontend/
    packages.json
    next.config.js

    public/                          # 靜態檔案
    pages/                           # 頁面
        index.js                     # 首頁
        api/                         # 後端 API 轉發
    core/                            # 功能 (無關 React)
        features/                    # 核心功能
            secretKeyManager.js
            sayingsManager.js
            ...
        utils/                       # 工具包
            generateRandomString.js
            ...
    components/
        layouts/                     # 頁面佈局
        features/                    # 功能組件
            AboutGod/
            MainBanner/
            ...
        base/                        # 基礎組件
            ...
        hooks/                       # 組件 hook
            useSecretKey.js          # 使用 SecretKey 的 Hook
            ...
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-3&quot;&gt;
&lt;h3&gt;後端&lt;/h3&gt;
&lt;p&gt;使用 fastapi / sqlalchemy 開發。&lt;/p&gt;
&lt;p&gt;專案架構:&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
backend/
    requirements.txt
    main.py                          # 啟始點
    dependencies.py                  # 相依
    routers/                         # 路由
        ...
    internal/
        bootstrap.py                 # 啟動服務
        core.py                      # 功能
        database.py                  # 資料庫相關
        models.py                    # 資料庫 Model
        schemas.py                   # schema
        utils.py                     # 工具包
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[淺談 JavaScript 的 Promise]]></title>
        <id>https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-javascript-%E7%9A%84-promise/</id>
        <link href="https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-javascript-%E7%9A%84-promise/"/>
        <updated>2018-02-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文會著重介紹 ES2015 定義的原生 Promise，除了基本的用法和個人使用心得外，還會補充一些原生 Promise 不足處的可能解決方案。適合完全沒用過 Promise 的生手閱讀，以上。
總之，讓我們開始吧！
＊＊＊
實用工具誕生的目的 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;本文會著重介紹 ES2015 定義的原生 Promise，除了基本的用法和個人使用心得外，還會補充一些原生 Promise 不足處的可能解決方案。適合完全沒用過 Promise 的生手閱讀，以上。&lt;/p&gt;
&lt;p&gt;總之，讓我們開始吧！&lt;/p&gt;
&lt;p&gt;＊＊＊&lt;/p&gt;
&lt;p&gt;實用工具誕生的目的，就是為了解決我們生活中碰到的種種問題。&lt;/p&gt;
&lt;p&gt;身為宅男工程師，我們碰到最大的問題就是－－沒女朋友。&lt;/p&gt;
&lt;p&gt;當然不是說 Promise 可以充當女友，畢竟也沒悲情到這種地步……吧？也……也許？嗯嗯……但善用 Promise 可以解決許多我們在「追女友」遇到的麻煩。&lt;/p&gt;
&lt;p&gt;怎麼說呢？事情是這樣子的……&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h2&gt;妹子的矜持 - 事情不是同步的&lt;/h2&gt;
&lt;p&gt;首先，理想中追女友的情況：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 告白服務由「心儀妹子」獨家提供&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 耶~去看電影！&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 哭哭，去酒吧買醉 T^T&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但事情遠非如此簡單。&lt;/p&gt;
&lt;p&gt;現實中，妹子並不會立即回覆「告白」，她們需要時間醞釀、考慮、權衡利弊，經過閨密投票等多道複雜手續，才能給予最後審核結果。&lt;/p&gt;
&lt;p&gt;即使妹子心中早已有答案，流程也是必要的。縱然心中千想萬想，仍得故作矜持，給予懸念。如此你在得到她後，能更加珍惜；反之，她完全看不上你，同樣也得等一段時間，表示她「真的」有在考慮……&lt;/p&gt;
&lt;p&gt;是以現實妹子實際提供的「介面」更像是這般：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 心儀妹子透過「回覆函式」事後回傳結果。&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;回覆函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 耶~去看電影！&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 哭哭，去酒吧買醉 T^T&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;「告白」函式執行後，不會立即回傳結果，而是透過「回覆函式」回傳。此種開發風格稱作 CPS (Continuation-passing style)。在 JavaScript 的世界，常用這種風格解決異步操作的問題。&lt;/p&gt;
&lt;p&gt;如此一來，心儀妹子不必馬上回應，能有時間更從容應對，並在想清楚答覆後，利用「回覆函式」回傳－－最終審判結果。&lt;/p&gt;
&lt;p&gt;但是－－只要和妹子扯上關係，事情就絕對不會簡單！&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;cps&quot;&gt;
&lt;h2&gt;妹子的複雜性 - CPS 風格不好寫&lt;/h2&gt;
&lt;p&gt;並非 CPS 不好，但要寫得好不容易。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;難懂的非線性思維 - 現在和未來的程式碼寫在一起&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;「我喜歡妳！」&lt;/p&gt;
&lt;p&gt;「如果結婚的話，我希望還能有自己的空間……」&lt;/p&gt;
&lt;p&gt;「什麼？」&lt;/p&gt;
&lt;p&gt;「有了空間後，我想要養小雞！」&lt;/p&gt;
&lt;p&gt;「為什麼要養小雞！？等等……結婚！所以妳是答應了嗎？」&lt;/p&gt;
&lt;p&gt;「不，那是未來結婚的話，但我們現在還沒交往。」&lt;/p&gt;
&lt;p&gt;「……那妳願意和我交往嗎？」&lt;/p&gt;
&lt;p&gt;「不願意。」&lt;/p&gt;
&lt;p&gt;……&lt;/p&gt;
&lt;p&gt;我們不太擅長異步的思考，偏偏 CPS 風格常迫使我們用跳躍式的寫法。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 時間點 1&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;回覆函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 時間點 3&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;結婚服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;結婚&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;回覆函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;老婆&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 時間點 5&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;養雞服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;養雞&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;回覆函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;雞群&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 時間點 7&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 時間點 6&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 時間點 4&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 時間點 2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;長久以來，我們習慣程式碼由上而下依序執行，但這種寫法的程式碼卻可能先執行頭尾，最後才執行中間。不同執行時間的程式碼夾雜在一起，不容易讓讀者理解。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h3&gt;困惑的百千種風格 - 操作方式不一致&lt;/h3&gt;
&lt;p&gt;即使無邊無際的花海，也沒有一朵花完全相同。妹子各有不同的喜好，其「告白介面」自然也不會一樣。同是追女友，卻得使用各種不同方式追，增添許多人的困擾。&lt;/p&gt;
&lt;p&gt;好比說指定「回覆函式」：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 可能以「有沒有成功？」的方式回覆&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;告白服務1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;回覆函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 成功脫單&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 脫單失敗&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 可能以「有沒有失敗？」的方式回覆 (如果沒失敗就是成功)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;告白服務2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;回覆函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 成功脫單&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 脫單失敗&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 可能不直接以回覆函式回傳結果&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;告白服務3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;回覆函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;yes&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 成功脫單&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 脫單失敗&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 可能回傳給不同函式&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;告白服務4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;告白服務4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;yes&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;成功函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 成功脫單&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;告白服務4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;no&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;失敗函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 脫單失敗&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;由於不必即時回覆，因此「回覆函式」指定的順序、方式都沒差別，連「回覆函式」本身的行為也能有不同的變化，加總起來足以讓人眼花撩亂。&lt;/p&gt;
&lt;p&gt;但其實「告白方式」和「告白內容」應該是可以區別的，重要的應該是「內容」而非「方式」才對。多餘的「方式」只是增添彼此的障礙罷了。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-3&quot;&gt;
&lt;h3&gt;陰險的不可預測性 - 回調不代表異步&lt;/h3&gt;
&lt;p&gt;妹子通常都是第三方函式庫，因此不保證行為如你預期，或著該說－－保證不如你預期！(笑)&lt;/p&gt;
&lt;p&gt;由於無法管控妹子如何實作「告白」，所以各種可能都會發生。假使她偏用同步的方式使用回調，你也拿她沒辦法。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//現在發生的事&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;回覆函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 可能是未來發生，也可能是立刻就發生，端看實作告白的人而定&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 還是現在發生的事&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;妹子一句「你真討厭～」，可能代表「喜歡你」，也可能代表「真的討厭你」，表面完全相同的形式卻可能有完全不同的意涵，這－－就是妹子的不可預測性！&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-4&quot;&gt;
&lt;h3&gt;眼花的多段式操作 - 回調地獄&lt;/h3&gt;
&lt;p&gt;總之，我們可以先不用著急最後答案，畢竟妹子需要時間考慮，必須等待異步的結果。&lt;/p&gt;
&lt;p&gt;但我們仍可先想像「如果有女友的話，接下來該怎麼做？」畢竟希望自己有女友，必定是有很多「如果有女友，我想做……」的事情。 (害羞)&lt;/p&gt;
&lt;p&gt;比如說－－結婚！俗話說得好－－不以結婚為目標的交往都是詐欺！&lt;/p&gt;
&lt;p&gt;(誰說的啊！？)&lt;/p&gt;
&lt;p&gt;雖然並非每對情侶都能走到這一步，但相信大家都是以此為目標一同前行的！&lt;/p&gt;
&lt;p&gt;結婚前要先求婚，求婚前總要先交往。我們不會搞錯順序，先結婚再求婚，也不會先求婚再交往。而且是前者確實完成後，才會有後續，不能同時進行。畢竟總不能和對方說：「我想和妳交往、求婚和結婚，妳願意同時和我交往、求婚和結婚嗎？」&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;回覆函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;求婚服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;求婚&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;未婚妻&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;未婚妻&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;結婚服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;結婚&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;未婚妻&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;老婆&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;老婆&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;當事情必須一個接著一個執行，後面的回調會用到前面回調的結果時，當層數一多，便能見到所謂的「回調地獄 (callback hell)」。這種結構很難平行展開，時常會越疊越深，因此很容易變得複雜而難懂。&lt;/p&gt;
&lt;p&gt;此外，事情也可能會失敗，假使對方嫌你求婚時不夠浪漫，可能就得再來一次。所以這裡用的 if 還可能再接別的 else，else 再接別的回調函式……&lt;/p&gt;
&lt;p&gt;配合前述的幾個麻煩混合在一起，「回調地獄」可不是浪得虛名的，甚至還有網站「 &lt;a class=&quot;reference external&quot; href=&quot;http://callbackhell.com/&quot;&gt;Callback Hell&lt;/a&gt; 」專門在說明追妹子的種種辛苦……種種「地獄」的感受……&lt;/p&gt;
&lt;p&gt;如果此時有英勇人士，能跳出來解決廣大宅男們的困境，應該會被奉為救世主吧？&lt;/p&gt;
&lt;p&gt;然後……還真有人跳出來了，他揮灑著熱汗，揮著手，堅定地從遙遠的地平線跑過來。&lt;/p&gt;
&lt;p&gt;遠遠的他好像在吼叫？他究竟想說什麼呢？&lt;/p&gt;
&lt;p&gt;閉上眼，仔細聆聽，字句似乎越來越清晰？&lt;/p&gt;
&lt;p&gt;「看標題！看標題！看標題！看標題！……」&lt;/p&gt;
&lt;p&gt;看什麼標題？&lt;/p&gt;
&lt;p&gt;「看文章標題！看文章標題！看文章標題！」&lt;/p&gt;
&lt;p&gt;什麼文章標題？&lt;/p&gt;
&lt;p&gt;突然一個小男孩猛然頓悟，狠狠抱住身旁的小女孩，雙手亂摸亂抓，並大聲喊道：「Promise！Promise！Promise！……」&lt;/p&gt;
&lt;p&gt;這時圍觀的群眾們福至心靈，也一同吼道：「Promise！Promise！Promise！……」&lt;/p&gt;
&lt;p&gt;沒錯，拯救宅男的救世主－－ Promise 出現了！&lt;/p&gt;
&lt;p&gt;(然後小男孩就被小女孩痛扁了。)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;promise&quot;&gt;
&lt;h2&gt;公用的「告白平台」 - Promise&lt;/h2&gt;
&lt;div class=&quot;section&quot; id=&quot;promise-1&quot;&gt;
&lt;h3&gt;為何要用公用告白平台？ - 何謂 Promise？&lt;/h3&gt;
&lt;p&gt;嚴格來講，上面提的很多問題不見得真的是「問題」，至少並非是「事情做不到」，而更像是「怎麼做都行，所以不知道怎麼做」。大家皆可用自己的方式使用回調，反而造成使用上的總總困擾。&lt;/p&gt;
&lt;p&gt;而 Promise 其實就是一個第三方公正平台，除了提供一些更方便解決問題的方法外，更要緊的是提供了規範化的流程，讓大家能用統一的方式處理異步。&lt;/p&gt;
&lt;p&gt;有了「告白平台」，就能減少和不同妹子告白的成本。因為這個「平台」不是個別妹子做的，你很清楚不會發生不同妹子不同行為的情況。只要妹子有提供 Promise 的告白介面，那你就可以預期對方的行為，減少誤解、或被誤解的可能。&lt;/p&gt;
&lt;p&gt;你知道告白成功會發生什麼，即便失敗也能明確得到答案。&lt;/p&gt;
&lt;p&gt;這邊先簡單介紹 Promise 的來由。&lt;/p&gt;
&lt;p&gt;從前從前，有一個稱作 CommonJS 的組織先後提出了 Promises/A、Promises/B、Promises/KISS、Promises/C、Promises/D 等方案。其中我們只需要知道 Promises/A 就行了，這個方案主要定義了像是 Promise 物件為何，或是 then 大致的行為等。&lt;/p&gt;
&lt;p&gt;後來又有人根據 Promises/A 制定了 Promises/A+，方案除了一些細微的更動外，主要是更加詳細定義了 then 的行為細節。估計感覺不錯，所以有很多根據 Promises/A+ 標準的實作品紛紛冒出，像是 Q、Bluebird 等。&lt;/p&gt;
&lt;p&gt;然後……實在是很好用，也很多人在用，所以最後 Promise 被列進 ES2015 裡，修成正果，得道升天。&lt;/p&gt;
&lt;p&gt;目前除了 IE11 外，幾乎所有的瀏覽器已經內建支援 Promise 了。即使沒有，也可用第三方函式庫，原則上已經不需要擔心支援度的問題。&lt;/p&gt;
&lt;p&gt;那這種方式到底是什麼呢？又好在那裡呢？&lt;/p&gt;
&lt;p&gt;讓我們先來看看使用 Promise 的例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;你&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;成功函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;失敗函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;失敗原因&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;與之前回調函式的版本差異不大。成功時，會執行 then 傳入的「成功函式」；異常時，會執行 catch 的「失敗函式」。&lt;/p&gt;
&lt;p&gt;但 Promise 的版本保證「妹子一定是以異步的方式」給予回覆。&lt;/p&gt;
&lt;p&gt;假使一位高富帥，腦袋聰明又努力，對待妹子浪漫又貼心，但即便被如此完美的白馬王子告白，妹子也沒辦法立刻搶著表示：「我答應！我答應！」&lt;/p&gt;
&lt;p&gt;這樣一來，至少你和高富帥都能一致的「異步」得到結果，而不是對你「同步」馬上拒絕……至少就不會發生前面提的「不可預測性」的問題了。&lt;/p&gt;
&lt;p&gt;此外，如果碰到前述需要一項接著一項做的事情，then 本身也可以再接別的 then，前一個 then 的回調函式執行完，就會執行接續下一個 then 的回調函式，前者的回傳值會當成後者的參數傳入，用先前的例子就會變成這樣：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;你&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;求婚服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;求婚&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;結婚服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;結婚&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;失敗函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;失敗原因&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看來多可愛？Promise 並不是要幹掉回調函式，而是能讓原先散落各處的回調函式，以非常一致且直觀的方式組織起來。你能清晰看出各個回調函式是如何串接執行的，不用再看到噁心的巢狀回調，而且處理失敗情況時也明確簡單許多。&lt;/p&gt;
&lt;p&gt;至於「告白」裡頭是怎麼實作？為什麼後面可以接 then 函式呢？&lt;/p&gt;
&lt;p&gt;原因在於「告白」函式裡頭回傳了一個「Promise 物件」。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;本妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;目標男&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;審核函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;是否審核通過&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;目標男&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;本妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 代表答應&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ne&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;你是個好人，但我一直都當你是哥哥……&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 裡頭寫失敗的理由。&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建立 Promise 物件時，會傳入一個 executor 函式(此例為「審核函式」)，函式有兩個參數，分別是「resolve」和「reject」兩個函式。&lt;/p&gt;
&lt;p&gt;此例中，若最後妹子選擇「答應」，就會在 exector 裡頭執行「resolve」函式。執行時可以傳值進去代表執行的結果，此例來說自然就是心愛的「女友」啦！反過來說，如果選擇「不答應」，那麼她就會執行「reject」函式，並傳入失敗的原因，習慣上「原因」會以 Error 物件的形式回傳。&lt;/p&gt;
&lt;p&gt;要注意 executor 函式本身會在建立 Promise 物件時立刻執行，但是結果(此例是「女友」)卻會是以異步的方式回傳(前面有強調過)。&lt;/p&gt;
&lt;p&gt;由於是異步，「告白」函式回傳的 Promise 物件，在當下可能有值也可能沒值。所以裡頭實際上存在著三種可能的狀態，分別是「fulfilled」、「reject」和「pending」。顧名思義，其分別對應著「實現」、「拒絕」和「擱置」或是更直白的說法即是「成功」、「失敗」和「等待中」。&lt;/p&gt;
&lt;p&gt;Promise 的狀態一開始會是「pending」，完成就會變成「fulfilled」。反之則變成「reject」，你可以查詢失敗的理由，想想為什麼會有這樣的結果和自己悲哀的人生……&lt;/p&gt;
&lt;p&gt;但至少不論是成功還是失敗，之後這個 Promise 物件的狀態都不會再改變，不可能有先是 fulfilled ，隔一陣子突然變成 reject 狀況，反之亦然。因此不會有「先答應你，事後再告你強暴……」的鳥事發生。&lt;/p&gt;
&lt;p&gt;說了這麼多，其實大部分的情況我們不太需要在意妹子具體怎麼實作「告白」的，畢竟通常我們就只是使用者，我們更在乎後頭 then 怎麼組織，裡頭的「成功函式」怎麼實作，不需要考慮 Promise 物件本身怎麼來的，只要用即可。&lt;/p&gt;
&lt;p&gt;需要在意的是妹子，因為她們是服務提供者，她們必須知道如何將自己的「告白服務」轉為 Promise 的型式。畢竟對她們而言，來源是多多益善，這樣比較容易找到理想的如意郎君，而不只有死宅男可以選。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;但事情也不是這麼絕對，因為「服務提供者」也不見得非要提供這種方式不可。所以有時候我們也需要自己將第三方服務包成 Promise 的形式，這時可以選擇某些第三方 Promise 庫提供的工具，方便我們將函式包成 Promise 的版本。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;then&quot;&gt;
&lt;h3&gt;如果告白成功呢？ - then&lt;/h3&gt;
&lt;p&gt;Promise 物件提供了成員函式 then，then 可以傳 onFulfilled 回調函式進去，若該 Promise 狀態變為 fulfilled，就會執行此回調函式。&lt;/p&gt;
&lt;p&gt;前例的告白函式成功執行時，會回傳一個「女友」(利用 resolve 傳遞，可以是任何東西，只是此例是「女友」)，接著「女友」就會被當成參數傳入 onFulfilled 函式。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;牽手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 「女友」會被當成「牽手」回調函式的參數。&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;執行 then 函式後本身會回傳另一個新的 Promise 物件，當 then 的回調函式執行成功時，這個新 Promise 物件的狀態就會變為「成功」，反之如果回調函式在執行過程中擲出異常，就會變為「失敗」。&lt;/p&gt;
&lt;p&gt;也因為 then 回傳的也是 Promise 物件，所以才能用 then 串接下去。此例來說，有女友後自然不會只是牽手，肯定還會有下一步，畢竟男人的欲望可沒這麼容易滿足的！那寫起來就會樣：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promise1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promise2&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promise1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;牽手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promise3&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promise2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;擁抱&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promise4&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promise3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;接吻&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 或合在一起&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;牽手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;擁抱&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;接吻&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;假設「告白」本身的結果是「成功」，那對應的 promise1 物件狀態就會變成「成功」，並執行「牽手」函式。如果接下來「牽手」也執行成功，就會換成 promise2 物件變成「成功」，並執行「擁抱」，以此類推。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;牽手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 「女友」是「告白」的回傳值&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 以某種方法和女友牽手 (羞)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 直接回傳，當成「擁抱」的參數&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;擁抱&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;接吻&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;catch&quot;&gt;
&lt;h3&gt;如果告白失敗呢？ - catch&lt;/h3&gt;
&lt;p&gt;有成功自然就有失敗，失敗也需要有對應的計劃，畢竟人生還是要過嘛！因此 catch 就可以出場了。與 then 相反，catch 可以傳入 onRejected 函式，顧名思義，會在上一個 Promise 失敗時執行。&lt;/p&gt;
&lt;p&gt;同樣的，catch 函式本身也會回傳新的 Promise 物件，因此也可以像 then 一樣不斷串接下去。&lt;/p&gt;
&lt;p&gt;舉個例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;失敗的補救方案&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// error 是「告白」回傳的 Promise 物件執行 reject 的回傳值&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 我只把你當哥哥&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;右手&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;牽手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 只要「失敗的補救方案」，一樣會執行接下來的「牽手」&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;擁抱&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;接吻&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;假設「告白」失敗，那傳給 catch 的「失敗的補救方案」函式就會被執行。此處很容易誤解的地方是－－雖然「告白」失敗了，但只要失敗時有正常執行「失敗的補救方案」，那麼對應的 Promise 本身就算是成功的！&lt;/p&gt;
&lt;p&gt;換言之，如要這裡的「失敗的補救方案」有正常回傳「右手」，之後還是會正常執行「牽手」函式，只不過傳入的參數自然就是你的「右手」囉。 (右手和自己牽手？)&lt;/p&gt;
&lt;p&gt;身為一個專業的宅宅，都應該要培養一個好習慣－－不管結果是什麼，即是再哀傷，最後都會用 catch 函式 接住所有可能的錯誤！這樣才是負責任的好宅宅！&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;善後&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 做為有負責任的宅宅該做的事&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;說再深入一些，catch 函式其實只是 then 的語法糖，事實上 then 也可以將 onRejected 當作第二個參數，代表「如果失敗時會執行的回調函式」，其實也就是 catch 在做的事。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 前例可以改寫成&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;失敗的補救方案&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 完全等同於 catch&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;雖然通常我們只要用 catch 即可，但在 IE8 以下的瀏覽器中，由於 catch 和 try ... catch 的 catch 同名，會產生名稱方面的衝突，因此只能使用 then，不然會爆炸。但山不轉路轉，舉例來說，有的第三方 Promise 函式庫會改用 caught 這個名稱取代 catch。&lt;/p&gt;
&lt;p&gt;你可能會好奇，傳給 Promise 的 executor 函式可以用 reject 回傳失敗的結果，那傳給 then 的 onFulfilled 函式或 catch 的 onRejected 函式執行失敗的話又該怎麼做呢？&lt;/p&gt;
&lt;p&gt;其實無論是那一種都有一個很簡單的方式代表 Promise 執行失敗，那就是簡單的執出異常即可。&lt;/p&gt;
&lt;p&gt;executor 函式的例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;本妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;目標男&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;審核函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;目標男&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;兩大類&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ne&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;滾！廢物！&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 可以直接擲出異常&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;///...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;兩大類&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 可以用 catch 接到「告白」函式擲出的 error&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;onFulfilled 函式的例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;牽手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;       &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ne&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;雖然人家願意當女友，但是不給牽！&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 一樣可以用接到「牽手」函式擲出的 error&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通常兩種方法沒有任何差別，但在有些情況只有 reject 可以正常運作。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;本妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;目標男&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;審核函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ne&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;滾！廢物！&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 這裡如果 throw 沒有效果，只能用 reject&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;///...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不過正如先前所提，多數情況我們都只是「服務」的使用者，只會使用到 then 或 catch，所以前面的東西不用太在意，用熟就會了。只要知道實作 onFulfilled 或 onRejected 函式時，成功直接回傳，而失敗時就讓它執出異常即可。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;另外，其實還可以用後頭提的 Promise.reject 來做到 reject 的效果。&lt;/p&gt;
&lt;div class=&quot;last&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;牽手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ne&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;雖然人家願意當女友，但是不給牽！&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 一樣可以用接到「牽手」函式擲出的 error&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;接下來，讓我們深呼吸幾次，回過頭來整理一下發生了什麼，我們知道了為什麼要有 Promise？知道了什麼是 Promise 物件以及要怎麼使用？另外還了解了 then 和 catch 的使用方式。&lt;/p&gt;
&lt;p&gt;所以 Promise 就這樣了嗎？&lt;/p&gt;
&lt;p&gt;當然不是！&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-5&quot;&gt;
&lt;h3&gt;那如果不知道成功還是失敗呢？ - 回傳值&lt;/h3&gt;
&lt;p&gt;扯到交往，你以為事情會這麼簡單嗎？&lt;/p&gt;
&lt;p&gt;當然不會，人家可是妹子耶！&lt;/p&gt;
&lt;p&gt;你以為告白後，妹子就會立刻給予答覆嗎？&lt;/p&gt;
&lt;p&gt;當然不可能，人家可是妹子耶！&lt;/p&gt;
&lt;p&gt;你可能得等一段時間，異步等待妹子給予答覆。&lt;/p&gt;
&lt;p&gt;等到妹子回覆了，你以為這樣就有結束了嗎？&lt;/p&gt;
&lt;p&gt;當然不一定，人家可是妹子耶！&lt;/p&gt;
&lt;p&gt;可……可是不是成功就是失敗，難道還能有別的結果？&lt;/p&gt;
&lt;p&gt;當然有，那就是－－人家自己也還不知道！&lt;/p&gt;
&lt;p&gt;啥？&lt;/p&gt;
&lt;p&gt;其實妹子在實作告白時，最後不見得會直接回傳一個「女友」，而可以是另一個 Promise 物件。&lt;/p&gt;
&lt;p&gt;何解？&lt;/p&gt;
&lt;p&gt;因為對方可能早已有男友啦！所以她可能會說：「我得先和現任男友分手才能和你交往。」&lt;/p&gt;
&lt;p&gt;換言之，她必須要和男友解約成功了才能有後續，解約失敗當然就一切免談了。而和男友分手也需要時間，所以回傳另一個 Promise 物件給你。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;本妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;目標男&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;審核函式&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;是否審核通過&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;目標男&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 回傳另一個 Promise&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;和男友解約&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;是否解約成功&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;本妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 與男友解約成功的話就回傳女友&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}))&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解約當然也可能失敗，想當然爾，不管中間過程如何，這個 Promise 自然也就「失敗」了。&lt;/p&gt;
&lt;p&gt;說是這樣說，但其實使用者不需要管上一個 Promise 回傳的究竟是值還是 Promise。因為如果回傳的是 Promise 物件，那 Promise 會自動等到該 Promise 物件的結果出來後，才回傳最終結果。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;牽手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 成功時「牽手」收到的仍是「女友」而非 Promise 物件&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;擁抱&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;接吻&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;此例來說，即使「告白」的結果回傳的是另一個 Promise 物件，後面「牽手」函式其實接到的參數仍會是「女友」，而非中間過程的 Promise 物件。&lt;/p&gt;
&lt;p&gt;即使 Promise 物件的結果又是另一個新的 Promise 物件，它也會繼續找，不斷重覆下去，直到不是 Promise 的結果出現為止。&lt;/p&gt;
&lt;p&gt;也就是說 Promise 也可以寫得像回調地獄一樣，舉一個比較實際的例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// fetch 會抓取目標網址的內容，並且回傳 Promise&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;https://api.marco79423.net/api/articles/&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 拿到回傳值後，可以轉成 json 格式，一樣會回傳 Promise&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;articles&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;articles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 成功拿到內容&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但 Promise 的好處是可以平展開：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 也可以直接展開&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;https://api.marco79423.net/api/articles/&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;articles&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;articles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;而 CPS 的方式就不能如此，但也並非全展開就是好，因為有時適當的調整深度可以做到傳遞參數的效果，比如說剛剛例子中：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;牽手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 牽手必須將「告白」傳入的「女友」傳下去&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;擁抱&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;接吻&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;若 then 要可接下去，「牽手」、「擁抱」、「接吻」都必須將「女友」當成結果傳下去，不然後面的就沒有「女友」可以抱了。&lt;/p&gt;
&lt;p&gt;但如果這時改成：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;牽手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 可能要包成回傳 Promise 的版本&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;擁抱&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 可以直接拿到外面的「女友」&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;接吻&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;// 或是用後頭會提的 Promise.resolve&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;告白服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;牽手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;擁抱&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;接吻&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這樣「牽手」、「擁抱」和「接吻」就可以直接拿到上一層作用域的「女友」而不需要透過參數傳入了。&lt;/p&gt;
&lt;p&gt;整理一下，所以傳入 resolve 和 reject 函式的值和 then/catch 的回傳值，總共可以有三種類型，分別是：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;33%&quot; /&gt;
&lt;col width=&quot;67%&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead valign=&quot;bottom&quot;&gt;
&lt;tr&gt;&lt;th class=&quot;head&quot;&gt;類型&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;解釋&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;Value&lt;/td&gt;
&lt;td&gt;一般值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Promise&lt;/td&gt;
&lt;td&gt;Promise 物件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Thenable&lt;/td&gt;
&lt;td&gt;有實作 then 的物件&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Value 就是所謂的一般值，剛剛的例子就是「女友」，Promise 即是剛才提到的 Promise 物件。&lt;/p&gt;
&lt;p&gt;至於最後的 Thenable 則是為了相容之前的函式庫而定的。考慮到 Promise 是後來者，很多「準情侶」還不知道有這個好東西，但上天本身就會自己找一個出路。即使不是 Promise，但要是看起來很像 Promise，那就先假裝當 Promise 吧！雖然不是真貨，但假使人家明明都已經寫好黑紙白字「本姑娘在此鄭重宣示要當 XXX 的女友」，那當然就先相信嘍，不然要拒絕嗎？&lt;/p&gt;
&lt;p&gt;所以 Thenable 到底是什麼呢？就是只要有實作 then 函式的物件就是 Thenable，在此可以直接充當 Promise 的方式使用。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;promise-2&quot;&gt;
&lt;h2&gt;宅男告白利器 - Promise 常用工具&lt;/h2&gt;
&lt;div class=&quot;section&quot; id=&quot;promise-race&quot;&gt;
&lt;h3&gt;平行告白工具 - Promise.race&lt;/h3&gt;
&lt;p&gt;身為一位有自覺的宅男，你不用期待自己的告白一定能成功，為了增加命中率，實務上可能會採取多管齊下的方式同時進行。一般而言－－注意僅僅是一般而言，我們只要一位女友就滿足了。只要一位就可以鄙視其他所有程式語言、所有編輯器、IDE 的單身工程師了！&lt;/p&gt;
&lt;p&gt;一個成功就行，其他都可以不用管，這時就可以用 Promise.race。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;race&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;右手&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;     &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Promise.race 代表誰先成功誰上，可以接一個 array，裡頭同樣可以放 Value、Promise 和 Thenable 三種類型的值。畢竟每名妹子皆有不同，你不會期待每名妹子追的方式都一樣。如果是 Value 就表示直接成功，直接給你一個「女友」，Promise 和 Thenable 的話可能就要過五關斬六將，但不是不可能成功。&lt;/p&gt;
&lt;p&gt;原則上如果有 value 類型，幾乎就可以肯定是這個了。此例中，如果前面用的告白函式都不是直接回傳一個「女友」的話，最後結果就一定會是「右手」。&lt;/p&gt;
&lt;p&gt;Promise.race 也會回傳一個 Promise　物件，所以後面也可以串接 then，只要 array 裡有一個成功，這個 Promise 物件就會成功，並將該結果當成參數傳下去。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;如果有多個結果同時出來，那麼就會選擇 Array 中第一個成功結果。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;但世界是很黑暗的，有時最大的問題不是對方拒絕，而是連拒絕都不願意給！所有的「告白服務」 都在那邊耗時間，石沉大海，不說行也不說不行，存心就要玩你！&lt;/p&gt;
&lt;p&gt;這時就有一個簡單的技巧，就是另外加一個會 timeout 的 Promise 進去。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;race&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 這個 Promise 五秒後會回傳成功&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 失敗了 T^T&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這樣一來，至少五秒後就一定會看得到結果……雖然結果不一定是美妙的。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;話說回來，假設告白信送出去，還真有妹子願意答應你，而且還不只一位該怎麼辦？&lt;/p&gt;
&lt;p&gt;想腳踏兩條船？&lt;/p&gt;
&lt;p&gt;那你當然是死……不是啦，雖然目前原生 Promise 沒有「取消」的機制 (ES2015)，但如果有這個需求，可以選用第三方提供的 Promise 函式庫，像是 Bluebird 就有提供取消 Promise 的機制。&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;這樣至少能在 Promise 還沒真的運行時取消，但假使「告白信」真的已經送出去了……那就請自求多福吧。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;promise-all&quot;&gt;
&lt;h3&gt;通通要搞定工具 - Promise.all&lt;/h3&gt;
&lt;p&gt;即使一切順利，告白成功，對方願意當你的女友，故事也還遠沒有結束。畢竟－－搞定女友還要搞定她的家長啊！除此之外，她的親朋好友、乾哥乾姊等也都得解決。只要有一個沒成，結婚可能就沒戲。所以「準備」要齊全，做事才有把握。&lt;/p&gt;
&lt;p&gt;這時 Promise.all 就可以上場了，用法與 Promise.race 相似：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// Promise or Thenable&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白服務1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;告白&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;心儀妹子1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;車商&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;買車&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;所有錢&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;房產服務&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;申請房產證&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;我&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;借來的錢&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// Value&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;薪資條&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 之後的 then 同樣會以 array 的形式回傳每個 Promise 的值。&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(([&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;女友&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;車&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;房產&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;薪資條&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;與 Promise.race 相同，Promise.all 也會回傳一個 Promise 物件，與 Promise.race 不同的地方在於 Promise.all 要等到所有的結果都成功才算成功，反之都算失敗。&lt;/p&gt;
&lt;p&gt;另外 Promise.all 回傳的是整個 array 所有 Promise 和 Value 的最終結果。因此如果需要運行多個平行任務，並且要搜集結果回來時，就可以用 Promise.all 搜集所有的結果。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;promise-resolve-promise-reject&quot;&gt;
&lt;h3&gt;馬上得到結果工具 - Promise.resolve / Promise.reject&lt;/h3&gt;
&lt;p&gt;夢做完了，讓我們一起回到現實吧。&lt;/p&gt;
&lt;p&gt;有些答案一開始就是注定的，有些問題不用等待，答案早已知曉，告白？明明都已經知道答案了，何必再問？&lt;/p&gt;
&lt;p&gt;這時就可以使用 Promise.resolve 和 Promise.reject，兩者一樣會回傳一個 Promise 物件，但前者一開始就是成功的狀態，後者則一開始就是失敗的狀態。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;race&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ne&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;我只當你是哥哥&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ne&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;我只把你當成朋友&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ne&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;死變態&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ne&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;色狼&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;右手&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;當我們在學一件事物的時候要明白－－這個世上並沒有銀彈。工具雖然能輔助我們解決問題，但問題的本質並不會因此變得簡單，頂多較容易操作而已。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;「問題的本質是你只是死宅，僅此而已。」&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;「……不過沒關係，至少還有右手。」&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[學習筆記 - DCI]]></title>
        <id>https://blog.marco79423.net/articles/%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-dci/</id>
        <link href="https://blog.marco79423.net/articles/%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-dci/"/>
        <updated>2016-12-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[
DCI 是 Data、Context 和 Interaction 的簡稱，是一個看問題的視角，有點像是 MVC 的感覺，底層用什麼語言沒有分別，就實作而言，就是一種處理問題的方式。



DCI ...]]></summary>
        <content type="html"><![CDATA[&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;DCI 是 Data、Context 和 Interaction 的簡稱，是一個看問題的視角，有點像是 MVC 的感覺，底層用什麼語言沒有分別，就實作而言，就是一種處理問題的方式。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;2&quot;&gt;
&lt;li&gt;DCI 希望能讓物件能更容易對應到人的心智模型上。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;3&quot;&gt;
&lt;li&gt;傳統 OO 的問題是善於 capturing structure，但相對比較不容易 capture behavior，好比說演算法，我們常將演算法放在物件的 method 中，對於影響範圍是單一物件的時候可能不是什麼大問題，但比較大的演算法常包含物件之間的互動，這時就可能被迫將演算法拆分在多個物件中。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;4&quot;&gt;
&lt;li&gt;拆分的代價(或表現形式)就是你在看別人的程式碼時，你可能容易看得出資料的結構。但具體的行為，如「按下一個按鈕會發生什麼事情」，就必須進入每一個影響的物件才有「機會」了解。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;5&quot;&gt;
&lt;li&gt;覺得和 functional language 的戰點有點相似。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;6&quot;&gt;
&lt;li&gt;好的設計應該讓「不變的」將「常改變」的分開，理論上物件(類別)應該要是穩定的，所以我們常用「繼承」來解決這個問題，透過繼承我們可以讓物件(類別)穩定，修改的部分則在子類別實作。但有時會發生問題，好比說要新增功能時(method)該怎麼做？在子類別新增一個實作當然沒問題，但父類別不就沒有這個功能了？所以這樣父類別好像也得提供同樣的介面，甚或需要實作自己的版本。但這樣還稱得上讓物件(類別)穩定(stable)嗎？&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;7&quot;&gt;
&lt;li&gt;如果用上述的方式，隨著時間過去，功能的增加，物件(類別)只會越來越臃腫，最後會發現這個物件(類別)已經不是原來那個單純的意思了。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;8&quot;&gt;
&lt;li&gt;DCI 用一種類似「角色扮演」的方式處理這個問題，它認為 Domain Object (類似對比 MVC 的 Model) 應該要是單純的，dumb 的，當一個 smart data 即可，舉例來說「人」類別(中文舉例大好)。人本身沒有什麼意思，也沒有什麼功能。但他在人生中卻可以扮演很多角色。如「人」當「爸爸」的時候，就可以玩女兒，所以就有了「玩女兒」這個功能。而當「老公」的時候，就有了「當載具」的功能、當「Boss」的時候，就有的時候就有「欺負下屬」的功能等等。在 DCI 中，「爸爸」、「老公」、「Boss」都代表 Role。在不同的情境(Context)中，一個人可以代表不同的「Role」。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;9&quot;&gt;
&lt;li&gt;上例具體到程式時，會以類似下述的方式實作，在一個 Context 中，Domain object 會以「動態注入」的方式注入 Role 的 method。然後透過這些 method 在 Context 中實現所需要的功能。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;10&quot;&gt;
&lt;li&gt;Context 可以代表一個 User Story 或說是一個場景，它可能是一個類別、一個函式，反正知道意思就好。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;11&quot;&gt;
&lt;li&gt;有了場景後，就可以輸入對應的 Domain Object，這些 Domain Object 會根據在這個場景中扮演的角色(Role)，動作注入角色需要的功能(概念如此，可以以各種方式實現，比如說 Python 可以直接新增 method)，有了這些功能，這些角色就可以在場景出交互作用，完成這個 Story 發生的事情。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;12&quot;&gt;
&lt;li&gt;換言之，演算法會聚合在 Context 裡完成。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;13&quot;&gt;
&lt;li&gt;讓每一個 Context 代表一個 Story，除了好讀外，也適合應用 BDD&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;14&quot;&gt;
&lt;li&gt;動態注入 method 可以讓 Domain Object 保持單純，具體實作不同語言有不同的展現方式。python 有一個 roles 函式庫，C++ 可以用 template 等等。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;15&quot;&gt;
&lt;li&gt;一個物件可以扮演多個角色，一個角色也可以被多個物件扮演&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;16&quot;&gt;
&lt;li&gt;物件都可區分為：核心屬性和行為、場景屬性和行為。核心就是不依賴場景，與變化隔離；反之場景就是進入扮演角色時，動態注入的屬性和行為。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class=&quot;docutils&quot; /&gt;
&lt;ol class=&quot;arabic simple&quot; start=&quot;17&quot;&gt;
&lt;li&gt;DCI 的設計關鍵是 Domain Object 要非常單純，邏輯和行為則應該放在角色(Role) 裡頭，依附在場景之下。&lt;/li&gt;
&lt;/ol&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 Python 的生活日常系列 [2] - 超簡易資料分析]]></title>
        <id>https://blog.marco79423.net/articles/%E4%BD%BF%E7%94%A8-python-%E7%9A%84%E7%94%9F%E6%B4%BB%E6%97%A5%E5%B8%B8%E7%B3%BB%E5%88%97-2-%E8%B6%85%E7%B0%A1%E6%98%93%E8%B3%87%E6%96%99%E5%88%86%E6%9E%90/</id>
        <link href="https://blog.marco79423.net/articles/%E4%BD%BF%E7%94%A8-python-%E7%9A%84%E7%94%9F%E6%B4%BB%E6%97%A5%E5%B8%B8%E7%B3%BB%E5%88%97-2-%E8%B6%85%E7%B0%A1%E6%98%93%E8%B3%87%E6%96%99%E5%88%86%E6%9E%90/"/>
        <updated>2016-09-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近閒閒沒事做，想起前陣子看到的新聞評論－－ 如何看貧富差距？官員與學者的爭論 。
該文章認為許多爭論根本沒有意義，因為調查從一開始就充滿著統計問題：首先，台灣納稅人也不過一千三百萬人，要取得完整的母體資料並非不可能，為何只取一萬六千戶的人抽樣？如果能提供客觀的完整資料，就不需要爭執到底該用十等分還是二十等分的簡化。而且用收入的平均數表示結果就像男生和女生平均有一個睪丸一樣可笑……然後就是一推分析和討論。
不過文章的內容不是重點 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;最近閒閒沒事做，想起前陣子看到的新聞評論－－ &lt;a class=&quot;reference external&quot; href=&quot;http://www.thinkingtaiwan.com//content/5381&quot;&gt;如何看貧富差距？官員與學者的爭論&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;該文章認為許多爭論根本沒有意義，因為調查從一開始就充滿著統計問題：首先，台灣納稅人也不過一千三百萬人，要取得完整的母體資料並非不可能，為何只取一萬六千戶的人抽樣？如果能提供客觀的完整資料，就不需要爭執到底該用十等分還是二十等分的簡化。而且用收入的平均數表示結果就像男生和女生平均有一個睪丸一樣可笑……然後就是一推分析和討論。&lt;/p&gt;
&lt;p&gt;不過文章的內容不是重點，畢竟我並非專業，雖然聽起來好像有道理，但問題是其所用的數據難道就一定沒有問題嗎？他是否會像他自己說的那般操弄數據呢？這我可不曉得，所以我實在無法給予正確的評判，但這事確實勾起了我小小的好奇心……。&lt;/p&gt;
&lt;p&gt;說這麼多，讀者應該會以為小弟我接下來的文章就是分析這件事……可惜你猜錯了。&lt;/p&gt;
&lt;p&gt;因為當我興奮地開始查資料，才無奈的發現即使找遍了主計處、財政處等機關，我也無法找到所得的母體資料，只有已經處理過的統計資料，這種已經整理過的資料沒什麼意思，雖說中研院的「學術調查研究資料庫」好像有完整的版本，但似乎不是註冊網路會員就能拿得到……。&lt;/p&gt;
&lt;p&gt;不過這無關緊要，反正我既不是研究員，也不是趕畢業的研究生，我只是一個喜愛 Python、有點好奇心的無聊份子，我只為了好玩。&lt;/p&gt;
&lt;p&gt;而在這個過程中，我在「中華民國統計資訊網的統計資料庫」看到「 &lt;a class=&quot;reference external&quot; href=&quot;http://statdb.dgbas.gov.tw/PXweb/XMLFile/LaborForce/LM6201A1A.xml&quot;&gt;每人每月薪資&lt;/a&gt; 」的統計，比較這些年來各行業男女的平均薪資變化。我突然有些好奇我所處的行業薪水和其他行業比起來到底如何？男女薪資比又是如何？於是就決定下載來玩玩看。&lt;/p&gt;
&lt;p&gt;要分析資料之前，自然要先能處理對應的資料格式，通常這些格式不外乎就是 csv、xml、xls、json 等，常見的格式幾乎都有官方的標準庫可以直接使用，上述的四種格式中，也只有 xls 需要依靠第三方函式庫才能處理。但透過 pip 我們也可以很輕易地找到如 xlrd 之類的函式庫搞定這種格式。&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;52%&quot; /&gt;
&lt;col width=&quot;48%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;csv&lt;/td&gt;
&lt;td&gt;csv&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;xml&lt;/td&gt;
&lt;td&gt;xml&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;xls, xlsx&lt;/td&gt;
&lt;td&gt;xlrd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;json&lt;/td&gt;
&lt;td&gt;json&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一般而言，第三方的工具都會比標準庫好用(如果沒有比較好用，好意思拿出來嗎？)，尤其資料分析這一塊，我們有許多超強大的工具，像是 numpy、pandas、scipy 之類的工具來處理非常複雜的問題，不過為了讓本文更單純，我會盡可能的使用標準庫來解決，反正我也不是想分析什麼複雜的問題。&lt;/p&gt;
&lt;p&gt;而本文例子中「每人每月薪資」的資料格式是 xml，所以我就直接使用 xml 函式庫來處理。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;xml.etree.ElementTree&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;ET&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;tree&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;每人每月薪資.xml&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tree&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getroot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;以上的程式碼會將 xml 的內容轉為「樹」的結構。最外層的 tag 就是根節點。我們可以透過搜尋這個樹的節點取得 XML 對應位置的資料。要抓取指定節點的資訊，有一個很常見的方式即是 XPath (XML Path Langauge)，這是一種專門用來查詢 XML 資料的語言。完整的語法有些複雜，但了解基本語法後，幾乎足以滿足大部分的需求了。&lt;/p&gt;
&lt;p&gt;先舉一個簡單的例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;list&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;chicken&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;小白雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;weight&amp;gt;&lt;/span&gt;30&lt;span class=&quot;nt&quot;&gt;&amp;lt;/weight&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;height&amp;gt;&lt;/span&gt;7&lt;span class=&quot;nt&quot;&gt;&amp;lt;/height&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/chicken&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;chicken&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;小黃雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;weight&amp;gt;&lt;/span&gt;30&lt;span class=&quot;nt&quot;&gt;&amp;lt;/weight&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;height&amp;gt;&lt;/span&gt;7&lt;span class=&quot;nt&quot;&gt;&amp;lt;/height&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/chicken&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/list&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這個例子中，&amp;lt;list&amp;gt; 就代表根節點，裡頭的 &amp;lt;chicken&amp;gt; 都代表它的子節點，如果我們想抓取所有雞的體重，就可以透過 XPath 的語法「/list/chicken/weight」抓取，這句話的意思是說：「找出 list 節點下的 chicken 節點的 weight 節點」，類似檔案系統路徑的概念，應該不難理解。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;list&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;chicken&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;小白雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;weight&amp;gt;&lt;/span&gt;30&lt;span class=&quot;nt&quot;&gt;&amp;lt;/weight&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- 這個節點符合 --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;height&amp;gt;&lt;/span&gt;7&lt;span class=&quot;nt&quot;&gt;&amp;lt;/height&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/chicken&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;chicken&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;小黃雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;weight&amp;gt;&lt;/span&gt;30&lt;span class=&quot;nt&quot;&gt;&amp;lt;/weight&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- 這個節點也符合 --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;height&amp;gt;&lt;/span&gt;7&lt;span class=&quot;nt&quot;&gt;&amp;lt;/height&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/chicken&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/list&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;換言之，「/list/chicken/weight」就代表著條件，丟進對應的 XPath 處理器後，它就會把所有符合條件的節點找給你，很簡單吧？&lt;/p&gt;
&lt;p&gt;我們還可以用「中括號」加上別的限制條件，好比說想限制第二個 chicken 節點，就可以用「/list/chicken[2]/weight」表達。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;注意是由 1 開始算起。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;list&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;chicken&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;小白雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;weight&amp;gt;&lt;/span&gt;30&lt;span class=&quot;nt&quot;&gt;&amp;lt;/weight&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;height&amp;gt;&lt;/span&gt;7&lt;span class=&quot;nt&quot;&gt;&amp;lt;/height&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/chicken&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;chicken&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;小黃雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;weight&amp;gt;&lt;/span&gt;30&lt;span class=&quot;nt&quot;&gt;&amp;lt;/weight&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- 符合！ --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;height&amp;gt;&lt;/span&gt;7&lt;span class=&quot;nt&quot;&gt;&amp;lt;/height&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/chicken&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/list&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;除此之外，若想指定節點的屬性，則可以用「&amp;#64;」，如「/list/chicken[&amp;#64;name=&amp;quot;小黃雞&amp;quot;]/weight」代表只接受屬性 name 為「小黃雞」的 chicken 節點。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;list&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;chicken&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;小白雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;weight&amp;gt;&lt;/span&gt;30&lt;span class=&quot;nt&quot;&gt;&amp;lt;/weight&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;height&amp;gt;&lt;/span&gt;7&lt;span class=&quot;nt&quot;&gt;&amp;lt;/height&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/chicken&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;chicken&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;小黃雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;weight&amp;gt;&lt;/span&gt;30&lt;span class=&quot;nt&quot;&gt;&amp;lt;/weight&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- 符合！ --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;height&amp;gt;&lt;/span&gt;7&lt;span class=&quot;nt&quot;&gt;&amp;lt;/height&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/chicken&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/list&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;接著觀察「每人每月薪資」檔案格式，內容大概長這樣：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;GenericData&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Header&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- ...不管他... --&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Header&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;DataSet&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Series&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ITEM=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;工業及服務業-合計(新台幣元)&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Series&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Series&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ITEM=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;工業及服務業-男(新台幣元)&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Series&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Series&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ITEM=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;工業及服務業-女(新台幣元)&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Series&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Series&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ITEM=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;工業部門-合計(新台幣元)&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Series&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Series&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ITEM=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;工業部門-男(新台幣元)&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Series&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Series&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ITEM=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;工業部門-女(新台幣元)&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Series&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- ...後面都差不多... --&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;包含各行業的總計、男、女的薪資資料，其中每一個項目又可以分成「原始值」和「年增率」兩種資料，如：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Series&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ITEM=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;工業及服務業&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;SeriesProperty&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TableName=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;每人每月薪資&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;FREQ=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;DECIMALS=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SERIESTYPE=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;原始值&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SOURCE=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;行政院主計總處&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;UNIT=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;新台幣元&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/SeriesProperty&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;SeriesProperty&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TableName=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;每人每月薪資&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;FREQ=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;DECIMALS=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SERIESTYPE=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;年增率(%)&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SOURCE=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;行政院主計總處&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;UNIT=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;新台幣元&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/SeriesProperty&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Series&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;隨便抓一個的原始值內容長這樣：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;SeriesProperty&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TableName=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;每人每月薪資&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;FREQ=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;DECIMALS=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SERIESTYPE=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;原始值&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SOURCE=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;行政院主計總處&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;UNIT=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;新台幣元&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- ...很多年的資料... --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Obs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TIME_PERIOD=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;2012&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;OBS_VALUE=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;45589.0000000001&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Obs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TIME_PERIOD=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;2013&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;OBS_VALUE=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;45664.0000000001&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Obs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TIME_PERIOD=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;2014&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;OBS_VALUE=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;47300.0000000001&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Obs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TIME_PERIOD=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;2015&amp;quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;OBS_VALUE=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;48490.0000000001&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/SeriesProperty&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我手動整理一下裡頭的內容，共有下列這幾種行業：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
工業及服務業
    工業部門
        礦業及土石採取業
        製造業
        食品製造業
        飲料及菸草製造業
        紡織業
        成衣及服飾品製造業
        皮革、毛皮及其製品製造業
        木竹製品製造業
        紙漿、紙及紙製品製造業
        印刷及資料儲存媒體複製業
        石油及煤製品製造業
        化學材料製造業
        化學製品製造業
        藥品及醫用化學製品製造業
        橡膠製品製造業
        塑膠製品製造業
        非金屬礦物製品製造業
        基本金屬製造業
        金屬製品製造業
        電子零組件製造業
        電腦、電子產品及光學製品製造業
        電力設備製造業
        機械設備製造業
        汽車及其零件製造業
        其他運輸工具製造業
        家具製造業
        其他製造業
        產業用機械設備維修及安裝業
        電力及燃氣供應業
        用水供應及污染整治業
        營造業
    服務業部門
        批發及零售業
        運輸及倉儲業
        住宿及餐飲業
        資訊及通訊傳播業
        金融及保險業
        不動產業
        專業、科學及技術服務業
        支援服務業
        教育服務業
        醫療保健服務業
        藝術、娛樂及休閒服務業
        其他服務業
&lt;/pre&gt;
&lt;p&gt;估計我大概是屬於「資訊及通訊傳播業」吧？看完這些資料，我有點好奇「資訊及通訊傳播業」和其他行業比起來到底如何呢？為了省麻煩，直接比較 2015 年的薪水好了。&lt;/p&gt;
&lt;p&gt;終於到了使用 Python 的時間了！&lt;/p&gt;
&lt;p&gt;先來抓取資料：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;xml.etree.ElementTree&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;ET&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;tree&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;每人每月薪資.xml&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tree&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getroot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;data_set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# findall 可以接受 XPath 語法，然後回傳所有符合條件的節點&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;series_node&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;findall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;./DataSet/Series&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;item_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;series_node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;ITEM&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# 先只計算合計，不管男女&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;合計&amp;#39;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;industry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;-合計(新台幣元)&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# data_set[行業] = 薪水&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data_set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;industry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;series_node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;./SeriesProperty[@SERIESTYPE=&amp;quot;原始值&amp;quot;]/Obs[@TIME_PERIOD=&amp;quot;2015&amp;quot;]&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;OBS_VALUE&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;現在各行業的薪水資訊都有了，再來根據「薪水」排序即可。不過這樣看起來就太遜了，不方便炫耀，所以果然還是要圖形化才行。&lt;/p&gt;
&lt;p&gt;至於要怎麼畫圖表呢？那自然就得用著名的 Matplotlib 函式庫了。&lt;/p&gt;
&lt;p&gt;Matplotlib 是一個專門畫圖表的工具，用法據說和 MATLAB 相當接近，對其使用者來說非常好學，可惜我沒用過 MATLAB，所以對我沒差。&lt;/p&gt;
&lt;p&gt;由於 matplotlib 功能超強大，幾乎什麼都可以畫，畫「點」、畫「線」、畫「面」都難不倒它，畫一張圖也可，畫多張圖也可，甚至多張圖重覆畫在同一張圖也可，因為什麼都可以，所以操作上稍微有點複雜。&lt;/p&gt;
&lt;p&gt;這裡直接用例子介紹：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 設定中文字體&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;font&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;font_manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FontProperties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fname&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;./mingliu.ttc&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 建立一個可以實際放圖表的地方 (figsize 可以指定大小)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;figure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 在上面建一個可以畫圖的區域 Ax (裡頭可以有自己的座標系什麼的)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_subplot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# 1 分別代表第幾行、第幾列和第幾個&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# matplotlib 提供多種函式可以畫不同的圖，此例為水平條形圖&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 第一個參數代表垂直方向的值，後者代表對應水平方向的值&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 兩者皆為 array-like 的型態。&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;barh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;industries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;salaries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 設定標題&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;歷年來「資訊及通訊傳播業」和其他行業比較&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontproperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 設定 x 軸的文字&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_xlabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;收入&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontproperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 設定 y 軸的大小限制&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_ylim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;industries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 設定 x 軸的文字&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_ylabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;行業類別&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontproperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 設定 y 軸那些值需要顯示(之所以加 0.5 是因為我想要讓文字顯示在中間)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_yticks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;industries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 決定實際顯示的文字&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;yticklabels&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_yticklabels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;industries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontproperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 回傳的物件還可以做更細的設定&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;target_label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yticklabels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;industries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;資訊及通訊傳播業&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;target_label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;red&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 顯示&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;雖然看起來很多行，但多數都是為了美觀而做的相關設定。概念上，首先就是要用建立一個 Figure，你可以指定大小、解析度等。&lt;/p&gt;
&lt;p&gt;然後在 Figure 上建立 Axe 或 Subplot，如果要直接指定具體的位置大小就用 Axe，而 Subplot 則可以讓你用「上下幾分之幾、左右幾分之幾的位置」方式指定。前者比較有彈性，後者很多時候用起來比較方便。&lt;/p&gt;
&lt;p&gt;接下來就可以在 Axe 上畫各種圖表，像是長條圖、折線圖、直方圖等。&lt;/p&gt;
&lt;p&gt;剛剛所提幾乎所有元素都可以再微調，這就很廢功夫了，可能需要查看官方文件才能了解。&lt;/p&gt;
&lt;p&gt;總之，最後畫出來的圖形長成這樣子：&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/225fce06-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/225fce06-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;可以看得得出來「資訊及通訊傳播業」排第四，第一名是「電力及燃氣供應業」而且還多出第二名不少；最後一名則是教育服務業。&lt;/p&gt;
&lt;p&gt;實話說我現在才知道原來「電力及燃氣供應業」這麼賺錢，不過仔細想想「基礎設施」賺錢好像也沒什麼不對，但我有一點一直搞不清楚，為什麼「教育服務業」這麼不賺錢，還有那麼多人想當、或是「被想當」老師呢？只能說這個世界充滿著各種神秘的現象。&lt;/p&gt;
&lt;p&gt;這份資料也有包含男生和女生的薪水，我也來試試歷年的「資訊及通訊傳播業」的男女薪資比為何，看看什麼時候咱們辛苦的程式工作者在未來有沒有女性數量大爆發的可能性？&lt;/p&gt;
&lt;p&gt;稍為改一下剛才的程式碼：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data_set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;series_node&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;findall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;./DataSet/Series&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;item_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;series_node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;ITEM&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# 偷懶做法，反正我只要資訊通訊傳播業&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;資訊及通訊傳播業&amp;quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;race&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 男 or 女&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse_salary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;OBS_VALUE&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;OBS_VALUE&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;expression&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;./SeriesProperty[@SERIESTYPE=&amp;quot;原始值&amp;quot;]/*&amp;#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data_set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;race&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse_salary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;series_node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;findall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;資料結構改一下，然後畫成折線圖：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;figure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_subplot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;race&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data_set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data_set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;race&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1973&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2016&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;race&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;歷年來「資訊及通訊傳播業」和男女薪水比較&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontproperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_ylabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;收入&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontproperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fontsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;upper left&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shadow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_texts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_fontsize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_font_properties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最後畫出來的圖形大概長成這樣子：&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/22602860-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/22602860-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;可以看出來，女生薪水增長的速度明顯比不上男生，男女薪水確實有落差，看來女性大爆發的未來還要再等等了。&lt;/p&gt;
&lt;p&gt;不過我個人還是很有信心！現在台灣女多男少，男生已經在不知不覺中變成少數族群了，再加上現在又出一位女性總統，女生還有什麼不可以做的？更何況這個行業並不是體力活(加班不知道算不算？)，男性並沒有特別的優勢，沒道理這個行業就該全是男性……所謂風水輪流轉，也許是時候該換男生被壓迫了喔喔喔！&lt;/p&gt;
&lt;p&gt;以上。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;不過說起來，這個行業薪水真的有這麼高嗎？&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;相關檔案：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://blog.marco79423.net/backend/static/22605af6-2cd0-11f1-bbd2-32abd871d697/&quot;&gt;第一個例子&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://blog.marco79423.net/backend/static/226a6316-2cd0-11f1-bbd2-32abd871d697/&quot;&gt;第二個例子&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[筆記 - 資料視覺化之理論]]></title>
        <id>https://blog.marco79423.net/articles/%E7%AD%86%E8%A8%98-%E8%B3%87%E6%96%99%E8%A6%96%E8%A6%BA%E5%8C%96%E4%B9%8B%E7%90%86%E8%AB%96/</id>
        <link href="https://blog.marco79423.net/articles/%E7%AD%86%E8%A8%98-%E8%B3%87%E6%96%99%E8%A6%96%E8%A6%BA%E5%8C%96%E4%B9%8B%E7%90%86%E8%AB%96/"/>
        <updated>2016-04-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[在工作上有時會碰到「如何將大量的資料用簡單易懂的方式呈現」的問題，一直覺得是一個不大不小的困擾。正好聽聞了今年台灣資料科學愛好者年會舉辦的「資料視覺化之理論、賞析與實作」的課程，講師是康仕仲博士。本著閒著也是閒著，不聽白不聽的精神，參加這次一天的課程，不貴，還算是不錯的活動。
這篇主要的內容便是上課的心得和一些筆記 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;在工作上有時會碰到「如何將大量的資料用簡單易懂的方式呈現」的問題，一直覺得是一個不大不小的困擾。正好聽聞了今年台灣資料科學愛好者年會舉辦的「資料視覺化之理論、賞析與實作」的課程，講師是康仕仲博士。本著閒著也是閒著，不聽白不聽的精神，參加這次一天的課程，不貴，還算是不錯的活動。&lt;/p&gt;
&lt;p&gt;這篇主要的內容便是上課的心得和一些筆記。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h2&gt;資料視覺化的意義&lt;/h2&gt;
&lt;p&gt;資料視覺化基本概念就是－－當資料很多，內容很複雜時，透過各種圖像或是互動的方式，幫助人們理解資料。將冷冰冰的數字轉化為圖像，不但專家能夠看得開心，而且也能讓普通人也能有理解資料的機會。&lt;/p&gt;
&lt;p&gt;人類天生擁有許多天然的特性，有擅長與不擅長的事物。看到一百元紙鈔沒什麼感覺，但若你神經地把它全換成一元硬幣，就能真切地感受到它沉甸甸的重量……和自己的神經病。&lt;/p&gt;
&lt;p&gt;雖然一百元紙鈔和一百個一元硬幣明明有一樣的意義，但換成一百個一元硬幣後，我們就能更輕易的理解……原來那張紙真的代表了「一百個」一元耶！&lt;/p&gt;
&lt;p&gt;資料視覺化就是利用人類的各種天生的特性，將資料轉化為更方便我們理解的形式。&lt;/p&gt;
&lt;p&gt;有什麼好處？&lt;/p&gt;
&lt;p&gt;對專家而言，首先可以節省他們判斷資料的時間，再者，因為透過不同面向的閱讀，也可能藉此找出自己的思考盲點，發現問題，找到隱藏的關聯性等；對普通人如我來說，圖像化可以增加我閱讀的意願，有利於資訊的傳播，而且也能減少我閱讀的障礙，因為那些專家將資料轉化為圖像時，便已融入了豐富的資訊，讓我可以用比較接近專家的角度看問題。別的不提，至少我也知道「紅色粗體的大字」代表重要嘛！&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/234b69f6-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/234b69f6-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;有些人會覺得資料視覺化不過是美工的工作，不需要多深的專業。&lt;/p&gt;
&lt;p&gt;但顯然並非如此，由上述可知，資料視覺化的目的並不僅僅是為了「好看」，更關鍵是為了「好懂」！前者相對容易，但後者則必須花上不少心思。要使別人好懂，自己也要先了解才行，如果連自己都弄不明白，那你做的圖表還稱得上是「好懂」嗎？&lt;/p&gt;
&lt;p&gt;……可惜這個「有些人」通常位置都不低，擁有決策的權力，所以美工可能要辛苦一點了。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;當然不是講師說的，這是「心得」嘛！&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;當然了，實務上要求資料視覺化的製作者要完全了解資料本身，顯然也是不切實際的，但至少要能做到使用者滿意才行。許多領域都信奉著共同的道理，「使用者」才是工具存在最根本的目的。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-3&quot;&gt;
&lt;h2&gt;人類的擅長和不擅長&lt;/h2&gt;
&lt;p&gt;前面說過，人類有其擅長和不擅長，不擅長的先不提，擅長的則可以好好利用。一份非常複雜的資料，如果用人類最擅長的方式閱讀，是不是就相對容易理解？&lt;/p&gt;
&lt;p&gt;假設我和女友要比較身高差異，用長條圖就會比圓餅圖清楚。因為人類對長度比對角度敏感，用長條圖便可很輕易的看兩者的比例差異。反過來說，若我和女友比較的是體重差異時，用圓餅圖的效果就會比較差，這並不是說長條圖比圓餅圖好，不同的使用目的自有其最適合的表示法，圓餅圖在比較整體比例時就比長條圖有用。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;不要笑，我知道我沒有女友。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;要成為好的設計師，就必須理解這些人類的擅長和不擅長，把這些元素集合起來，就可以成為自己手中的武器。其重點主要有三，分別是：顏色、形狀和動作。其中最關鍵的就是顏色，恐怕可以佔有一半的重要性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;顏色&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;顏色最能抓住讀者的注意，速度甚至快到「讀者都沒開始理解其中的意義時，就已經進腦袋裡了」。也因為顏色非常「強烈」，所以常用來表示資料最重要的維度。&lt;/p&gt;
&lt;p&gt;好的設計師不隨便使用顏色，因為顏色太過醒目，讀者很容易分心。圖表上如果有太多顏色，讀者會無法分辨何者為重要，何者為不重要。而且咱們的腦袋時常會偷偷越過我們的理性，自行判讀顏色的意義，假若其中的意義和資料本身不相符合，就會使人困惑。&lt;/p&gt;
&lt;p&gt;彩虹色便是錯用顏色的經典例子，許多人會用彩虹色來製圖表，認為這是物理上波長連續的一系列顏色，所以代表一系列的數值非常符合自然。&lt;/p&gt;
&lt;p&gt;聽起來好像有道理，但可惜人類喜歡砍樹，不喜歡自然，人類的視覺系統並不認為彩虹是連續的一整體，而會自動為顏色分群，我們會潛意識為這些顏色賦予意義，而這個意義不見得與資料相符合。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/234c02bc-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/234c02bc-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;(我們會很自然地把左邊紅燈分成一群，右邊的靛紫分成一群)&lt;/p&gt;
&lt;p&gt;所以顏色非常「危險」，若不謹慎使用，可能會讓讀者錯誤理解資料。但反過來說，如果我們善用顏色，就可以有一加一大於二的效果。好比說我們可以使用在非常緊急，需要快速判斷的地方，像是當災難發生時，直接用紅色的警示燈，就會比用大大的新細明體寫：「哎呀，要爆炸了，快逃呦！」的效果還要好。&lt;/p&gt;
&lt;p&gt;顏色大致可以分為四種使用方式：「標註」、「度量」、「仿效」和「裝飾」。「標註」即是我們可以把顏色當成一個「名詞工具」，最經典的例子即是股票，透過顏色來代表：「漲」、「跌」兩種概念；「度量」則代表我們可以用顏色表達「量」的資訊，好比說溫度計，越冷越藍，越紅越熱；「仿效」則是利用顏色來仿照真實的顏色，如用藍色代表海水、用綠色代表草原等；最後是「裝飾」，可以利用顏色讓圖更加好看，更加有吸引力，但最好小心用，因為很容易傷害圖的易懂性。&lt;/p&gt;
&lt;p&gt;別忘了，美觀只是資料視覺化的要素之一，如果好看可是看不懂，那就是本末倒置。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;形狀&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;這裡說的形狀非常籠統，形狀、角度、距離、方向等都包含在內，這些要素都能抓住讀者的注意，不過人類對不同的要素敏感度不一。比如說人類對「長度」的判斷敏感，因此當有資料要比較時，長度是不錯的選擇。相對而言，人對角度雖然也算敏感，不過沒有像對「長度」的感受那麼細緻。所以要比較差異時，長條圖會比圓餅圖還要好，但圓餅圖還是有自己的優勢，在看整體比例時，圓餅圖效果還是很不錯的。&lt;/p&gt;
&lt;p&gt;相比起來，人對面積就不是這麼敏感，不容易判斷資料差異的倍數。如果有一大一小的圖形，我們會傾向低估大圖形的面積，以為大的沒這麼大。再強調一次，依照不同的使用需求，每一種方式都有意義，讓讀者的判斷力下降說不定也可以是一種策略……我絕對不是指公司財報什麼的……。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/234b298c-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/234b298c-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;(我們很難輕鬆地判斷兩個圖案面積的比例)&lt;/p&gt;
&lt;p&gt;這裡粗略的列一下不同要素人類判斷的精準度。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
位置 &amp;gt; 長度 &amp;gt; 斜度 &amp;gt; 角度 &amp;gt; 面積 &amp;gt; 體積 &amp;gt; 色彩飽和度

(精準到不精準)
&lt;/pre&gt;
&lt;p&gt;換言之，以後要做公司財報時，用色彩飽和度來表示利潤下降的比率就是一個不錯的選擇……當然前提是不要被人轟下台才行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;動作&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;人對會動的事物也會有反應，這也是初學者特別喜歡使用的方式，希望藉此讓圖表看起來豐富多采……來掩飾貧乏的內容。但事實上，這種方式可能不但沒有加分，還會反過來扣分。因為我們的腦已經演化出一種精密的方式可以自動地篩選動態的資訊，以幫助我們在很短的時間內做出正確的判斷，所以如果錯誤的使用動畫，反而可能會干擾資訊解讀。&lt;/p&gt;
&lt;p&gt;這裡有幾個因素會影響人的判斷。首先是「變盲(Change Blindness)」，變盲代表觀察者無法察覺的動態改變。簡單來說就是觀察的東西變了，但是觀察者卻沒發現的現象。為何會如此呢？通常是因為為了維持多目標的注意力，或是畫面閃爍有關。另一個因素是「動盲(Motion-Induced Blindness)」，動盲即是當在動態的背景下，注視特定的部分，會造成在不特定的時間內，忽略一些明顯的特徵。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/234b9d4a-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/234b9d4a-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 300px;&quot; /&gt;
&lt;p&gt;(轉自 &lt;a class=&quot;reference external&quot; href=&quot;https://www.wikiwand.com/en/Motion-induced_blindness&quot;&gt;維基&lt;/a&gt; ，這是一個動盲的實驗，如果專注地看圖中央閃爍的綠點一小段時間，就會發現旁邊的黃點會莫名的消失)&lt;/p&gt;
&lt;p&gt;這兩個因素都與人的注意力有關，當大腦在處理動態資訊時，會遵循「贏者通吃」的法則，時常會只注意到其中的某一個變化，而忽略其他的。這給我們的啟示即是盡量不要讓讀者同時注意多個目標，不然很容易被讀者直接忽略。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;小結&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;人類是非常容易分心的動物，過多的雜訊會影響我們的判斷力。所以我們設計時要反過來，簡化雜訊，強化目標。不要錯用顏色，也不要用過多的顏色。如果有東西會動，不要多個東西一起動。對於目標物，我們要用最適宜的形狀或是顏色呈現。如果有需要的話，倒是可以同時將多種不同的要素集合在同一個目標物上，可以有更加強調的效果。&lt;/p&gt;
&lt;p&gt;好比說大字體的「我想要女友」雖然已經很顯眼了，但如果是紅色大字體而且再加上燃燒動畫的「我想要女友」自然更加引人矚目。&lt;/p&gt;
&lt;p&gt;善用人類天生的特性，是好設計師的基本功。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-4&quot;&gt;
&lt;h2&gt;設計的原則和方法&lt;/h2&gt;
&lt;p&gt;複雜的資料不容易理解和表達，許多人就會嘗試先簡化資料。&lt;/p&gt;
&lt;p&gt;舉個例子，在計算貧富差距時……別誤會，我絕對沒有在討論政治……總之，計算方式即是將所有人的所得按收入排序，依高至低分為五組分別平均起來，然後最高除以最低得出一個數，這個數如果太高即代表貧富差距太大。因為只有一個數，所以很容易以視覺化呈現，這即為一種簡化。&lt;/p&gt;
&lt;p&gt;簡化資料代表有處理過，根據其處理的方式，許多事情便被隱藏起來了，這裡頭到底有多少意涵，讀者是很難經由簡化的數值看出來的。所以才會造成同樣的資料，因為處理方式不同，判斷結果也不同的情況。不過這樣政治人物們也才有材料可以吵，顯得他們好像不是無所事事的樣子。&lt;/p&gt;
&lt;p&gt;簡化資料會出現上述的問題，因此好的設計並不是簡化資料，而是盡可能的善用人的視覺理解力來降低閱讀門檻。當然，若是太過複雜的資料，降低門檻有時不容易做到，這時仍是需要一些簡化。所以這變成一種權衡，在設計師的能力範圍內，盡可能在讀者能理解的情形下呈現完整的資料。能力越強的設計師，呈現的資料就越完整，而且還能讓目標使用者看得清清楚楚。&lt;/p&gt;
&lt;p&gt;接下來，注意我剛剛提的「目標使用者」，資料視覺化的關鍵原則就是以「使用者為中心」，因為這是給使用者使用的。因此了解使用者的目的是很重要的。有時使用者的目的是為了快速理解資訊，快速判斷；有的是為了方便尋找關鍵或是發現問題；有的單純就是為了拿來說故事。&lt;/p&gt;
&lt;p&gt;了解使用目的後，我們就可以藉由草圖、原型來設計我們的圖表或是系統。關鍵是過程中最好要能與使用者不斷地溝通，滿足他們最真實的需求。&lt;/p&gt;
&lt;p&gt;進入實際的設計流程後，還有一些技巧可以參考：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;資料的運用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在設計之初，我們要盡可能的理解資料，然後選用適合的圖表表達資料的關聯性。每一種圖表都有其適合與不適合，無論是長條圖、圓餅圖、折線圖還是直方圖，這需要經驗和對各種圖表的熟悉掌握程度。這比較像是基本功，但用得好可以讓使用者更能輕易地理解資料。很多時候最簡單的方式就是直接下去試，直覺和經驗可以告訴我們其中的好與壞。參考一下別人的設計，也可以給你靈感，好的設計師就是要懂得如何偷別人的點子。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;空間的運用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但不用忘記所有的圖表都是在一個空間表達資料，在一個大的空間呈現和在一個小的空間呈現是不同的概念。在電腦呈現的方式不應該與手機相同，因為我們閱讀的方式並不一樣。所以在設計之時，也要考慮在資料顯示的情境為何，在捷運車上顯示的地圖資料，就不適宜用密密麻麻的文字表達。但同樣的資料若是用乾淨簡潔的大字顯示在論文中，只會讓教授覺得你在佔空間拼頁數。&lt;/p&gt;
&lt;p&gt;除此之外，適當的扭曲資料裡的空間有時也是一種表達資料的手段，一個經典的例子就是「捷運地圖」，台北捷運的路線顯然不都是直線，轉彎也不都是直角和四十五度角，但透過這種扭曲空間的方式，反而能方便我們理解。當然你也可以解釋說，因為捷運的實際的路線不是我們在乎的，所以被簡化了。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/234c2cba-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/234c2cba-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 400px;&quot; /&gt;
&lt;p&gt;&lt;strong&gt;互動的運用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最後則是互動的運用，設計師可以提供一些工具來讓讀者探索資料，讓使用者更能了解資料的本質。互動也可以提升趣味性，增加使用者的閱讀意願。沒人規定資料一定只能是死板板的，無聊的。像是著名的 3A 級遊戲「刺客教條」，除了遊玩本身，你也可以說它將歷史資料透過人實際操作互動的方式呈現在讀者(玩家)眼前，這也確實比一本大部頭的歷史研究資料更有互動性，不但較容易理解，而且更來得吸引人。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-5&quot;&gt;
&lt;h2&gt;總結&lt;/h2&gt;
&lt;p&gt;資料視覺化是盡可能的在不簡化資料的情況下，利用各種手段把閱讀門檻降低到目標使用者的程度。好的設計可以幫助人理解資料、增加閱讀意願、方便傳播等。這裡有一個共通的特性即是通常如果做得很好，讀者其實是沒感覺的。好比說交通狀態良好時，你不會記得什麼，但是一旦常發生塞車，你就會覺得某些政府長官很該死……。&lt;/p&gt;
&lt;p&gt;所以說－－做到沒人注意到，就是好的設計師……當然，也有可能是職業生涯不成功的設計師就是了。&lt;/p&gt;
&lt;p&gt;反正我不是設計師。&lt;/p&gt;
&lt;p&gt;以上。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[淺談 regex 及其應用]]></title>
        <id>https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-regex-%E5%8F%8A%E5%85%B6%E6%87%89%E7%94%A8/</id>
        <link href="https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-regex-%E5%8F%8A%E5%85%B6%E6%87%89%E7%94%A8/"/>
        <updated>2015-10-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[先別管 regex 了，你有用 Ctrl + F 搜尋過文章嗎？
一篇網頁文章中，搜尋「小雞」這個關鍵字，如果文章有出現這樣的字眼，就會移到出現「小雞」的位置 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;先別管 regex 了，你有用 Ctrl + F 搜尋過文章嗎？&lt;/p&gt;
&lt;p&gt;一篇網頁文章中，搜尋「小雞」這個關鍵字，如果文章有出現這樣的字眼，就會移到出現「小雞」的位置；如果沒有，它就會說找不到。&lt;/p&gt;
&lt;p&gt;一個簡單好用的功能。&lt;/p&gt;
&lt;p&gt;但人生很複雜，文章內容也有千千萬萬。&lt;/p&gt;
&lt;p&gt;當你搜尋「小雞」這個詞時，多半心裡想的是毛茸茸很可愛的「小雞」，所以當你發現找出來的結果是「小雞雞」時，心情就不會太好。但你也知道不能怪可憐的搜尋器，因為你心裡明白它是無辜的。&lt;/p&gt;
&lt;p&gt;又比如，「複雜的人生」有時會迫使你搜尋連自己也不十分確定內容的字串。&lt;/p&gt;
&lt;p&gt;反正你就是覺得小雞很可愛，具體是「小母雞」、「小公雞」、還是「小白雞」無關緊要，只要開頭為「小」，結尾是「雞」就行了，該怎麼搜呢？有點麻煩對吧？&lt;/p&gt;
&lt;p&gt;(咦！這樣「小雞雞」也符合定義耶？)&lt;/p&gt;
&lt;p&gt;又或者，你想搜尋小雞的相關資料，像是小雞的體重。搜「小雞」自然找不著，因為結果太多了，但搜「體重」也不對，萬一出來的是蟑螂的體重怎麼辦？多噁心？&lt;/p&gt;
&lt;p&gt;雖然你知道想搜尋的句子大致是「小雞 XX 公克重」這類的句子。但問題在於這個 XX 偏偏就不知道，若是知道何必搜尋？&lt;/p&gt;
&lt;p&gt;到了最後，發現自己不過只是想看一下可愛的小雞而己，竟然還能搞得這麼麻煩，人生實在有許多無奈。&lt;/p&gt;
&lt;p&gt;不管啦！老子就是想要知道小雞的體重啦！&lt;/p&gt;
&lt;p&gt;幸好，英勇的 regex 這時就現身了，regex 好棒棒！&lt;/p&gt;
&lt;p&gt;regex 是正規表示式(Regular Expression)的簡稱，我們只要使用正確的格式撰寫 regex，就可以直接透過對應的工具幫我們找到答案，現在的程式語言幾乎都有支援，有些甚至就直接就內建在語言裡頭了，好比說 Python 的 re 模組就是一個例子。&lt;/p&gt;
&lt;p&gt;與普通的搜尋相比，regex 的好處太多了，能夠輕鬆的表示多種、甚至無限種可能的字串，連不確定、不知道的字串也可以搜尋，好比說剛剛提到的「小雞」、「小白雞」、「小小雞」，甚至是不知道答案的「小雞 XX 公克重」都有辦法找到。&lt;/p&gt;
&lt;p&gt;有了 regex ，我們想要知道小雞的體重便容易許多了。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;regex-1&quot;&gt;
&lt;h2&gt;regex 語法簡介&lt;/h2&gt;
&lt;p&gt;regex 用法規則不少，小弟我把它大致區分為四種類別，分別是「選擇」、「次數」、「錨點」和「截取」類。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h3&gt;選擇&lt;/h3&gt;
&lt;p&gt;首先是「選擇」類，regex 可以在不知道準確字串的情況下對內文進行搜尋，不知道實際是小「白」雞、還是小「小」雞也沒關係，都可以搜尋得到，在此例中，我們可以這麼做：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小白雞|小小雞
&lt;/pre&gt;
&lt;p&gt;「|」代表「或」的意思，表示由「|」區隔出來的字串都可以接受，在這個例子中，無論是「小白雞」還是「小小雞」都可抓得到。&lt;/p&gt;
&lt;p&gt;我們也可以加上小括號可以限制「或」的範圍，達成同樣的效果：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小(白|小)雞
&lt;/pre&gt;
&lt;p&gt;這並不限制只能有兩種狀況，假設除了「小白雞」和「小小雞」，「小母雞」也是可以接受的，那只要再加上一個「|」即可：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小白雞|小小雞|小母雞
&lt;/pre&gt;
&lt;p&gt;或是&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小(白|小|母)雞
&lt;/pre&gt;
&lt;p&gt;其中 (白|小|母) 可以用中括號的語法簡化：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小[白小母]雞
&lt;/pre&gt;
&lt;p&gt;省去寫一大堆「|」的麻煩。&lt;/p&gt;
&lt;p&gt;不過，由於複雜的世界，可接受的情況越來越多，什麼小懶雞、小肥雞都出來了，這個中括號裡的內容可能也會越來越長。&lt;/p&gt;
&lt;p&gt;幸好中括號的用法還提供了另一個功能，那就是反向選擇的功能，假設今天我們已經豁出去，覺得什麼都可以，只要不是「小雞雞」就行！我們可以這樣寫：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小[^雞]雞
&lt;/pre&gt;
&lt;p&gt;在中括號內的開頭加上「^」代表反向選擇，只要不是括號內的字都可以接受。&lt;/p&gt;
&lt;p&gt;選擇的用法大概就這樣，非常單純。不過在實際的情況下，還是有些便利的技巧，比如說:&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
[a-zA-Z0-9]
&lt;/pre&gt;
&lt;p&gt;a-z 代表 a 到 z，A-Z 代表 A 到 Z 而 0-9 代表 0 到 9，這個 regex 代表「所有英文字母和數字」都可以接受。&lt;/p&gt;
&lt;p&gt;而有些太長用的字，甚至可以直接利用特殊的符號代表。下面列一個簡單的列表：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;23%&quot; /&gt;
&lt;col width=&quot;77%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;regex 語法&lt;/td&gt;
&lt;td&gt;意義&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;任意字元&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;\d&lt;/td&gt;
&lt;td&gt;數字(digit)，如 0 到 9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;\D&lt;/td&gt;
&lt;td&gt;非數字&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;\w&lt;/td&gt;
&lt;td&gt;文字(word)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;\W&lt;/td&gt;
&lt;td&gt;非文字&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;\s&lt;/td&gt;
&lt;td&gt;廣義的空白符號(whitespace)，如空白、tab 等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;\S&lt;/td&gt;
&lt;td&gt;非空白&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;通常 \w 代表的是 [A-Za-z0-9_]，但在 Python3 中，\w 也可以是中文字。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h3&gt;次數&lt;/h3&gt;
&lt;p&gt;再來則是關於「次數」類型的規則。&lt;/p&gt;
&lt;p&gt;有時不只出現搜尋的文字不確定，就連出現的次數也不能肯定。regex 也提供了一些特殊符號來處理這種次數未定的情況。&lt;/p&gt;
&lt;p&gt;好比說假設我們一開始就知道是小「白」雞，但卻發現「白」有機會不只出現一次，有可能是「小白雞」、「小白白雞」、「小白白白雞」、「小白白白白雞」……。&lt;/p&gt;
&lt;p&gt;這時就可以用「次數」類型的規則處理這個問題，在這個例子中，我們可以用：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小白*雞
&lt;/pre&gt;
&lt;p&gt;「*」修飾前面的「白」字，表示「白」可以出現任意次數(包含 0 次)，所以這個 regex 可以同時代表「小雞」、「小白雞」、「小白白雞」、「小白白白雞」……。&lt;/p&gt;
&lt;p&gt;如果不想包含 0 次，也可以用「+」代替：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小白+雞
&lt;/pre&gt;
&lt;p&gt;「+」一樣代表任意次數，但不包含 0 次。所以只能是「小白雞」、「小白白雞」、「小白白白雞」、「小白白白白雞」……。&lt;/p&gt;
&lt;p&gt;我們也可以直接指定可以出現的次數範圍：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小白{1,3}雞
&lt;/pre&gt;
&lt;p&gt;{} 大括號代表可以出現的次數範圍，這個例子即代表「白」可以出現 1 次到 3 次，所以只會有「小白雞」、「小白白雞」、「小白白白雞」三種情況。&lt;/p&gt;
&lt;p&gt;類似的規則可以見下表：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;34%&quot; /&gt;
&lt;col width=&quot;66%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;regex 語法&lt;/td&gt;
&lt;td&gt;意義&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;任意次數(包含 0 次)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;任意次數(不包含 0 次)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;0 次或 1 次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;{n}&lt;/td&gt;
&lt;td&gt;n 次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;{n, m}&lt;/td&gt;
&lt;td&gt;n 次到 m 次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;{n,}&lt;/td&gt;
&lt;td&gt;n 次以上&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-3&quot;&gt;
&lt;h3&gt;錨點&lt;/h3&gt;
&lt;p&gt;再來是定位相關的「錨點」類型。&lt;/p&gt;
&lt;p&gt;感謝我們身在一個複雜的世界，有時我們不但對搜尋的字串很龜毛，甚至對搜尋的字串出現的位置也有意見。這種類型的語法比較少，比較有機會用到的就那幾個。&lt;/p&gt;
&lt;p&gt;其中「^」代表開頭，「$」代表結尾。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
^小白雞
&lt;/pre&gt;
&lt;p&gt;代表「小白雞」一定要搜尋內文的開頭。&lt;/p&gt;
&lt;p&gt;(……話說這個例子還要用搜尋，也是一種本事？)&lt;/p&gt;
&lt;p&gt;反過來說：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小白雞$
&lt;/pre&gt;
&lt;p&gt;代表「小白雞」一定要在內文的結尾。&lt;/p&gt;
&lt;p&gt;另外還有比較常用的是「\b」和「\B」，前者代表「邊界」，後者代表「非邊界」。這個定義比較模糊，有時碰到像是中文的時候會出事，比較沒異議的應用場景比較像是應用在拼音文字單字的邊界，如：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
chick\b
&lt;/pre&gt;
&lt;p&gt;在這個例子中，「chicken」就不符合要求，因為「k」並非單字的邊界。但反過來如果是：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
chick\B
&lt;/pre&gt;
&lt;p&gt;那麼「chicken」就可以接受，但「chick」就不能過。但如果換成中文呢？比如說：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小雞\b
&lt;/pre&gt;
&lt;p&gt;據我測試的結果，有些可有些不行，像是 Python 2 就抓不到，但 Python 3 就可以。因此結論就是最好不要太依靠這個規則。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-4&quot;&gt;
&lt;h3&gt;截取&lt;/h3&gt;
&lt;p&gt;最後則是「截取」類的規則。&lt;/p&gt;
&lt;p&gt;小括號除了可以改變規則影響的範圍，像是下例 regex 對應的字串並非是「小雞雞」，而是「小雞小雞」:&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
(小雞){2}
&lt;/pre&gt;
&lt;p&gt;括號裡的內容本身也能直接當成結果輸出，說來有些抽象，舉個例子先：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小雞 (\d+) 公克重
&lt;/pre&gt;
&lt;p&gt;我們可以用這個 regex 搜尋文章中是否有對應的字串外，也可以直接截取括號裡的內容。如果符合的字串為「小雞 30 公克重」，我們可以直接取得「30」這個數字。&lt;/p&gt;
&lt;p&gt;事實上，小括號也不限於只能用一次：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小雞 (\d+) 公克重， (\d+) 公分長。
&lt;/pre&gt;
&lt;p&gt;這樣我們就能同時取得體重和身高了。&lt;/p&gt;
&lt;p&gt;接下來，再度因應複雜的社會，越來越機車的情況一個接著一個出現，有時我們會碰到需要考慮「前後文」的字串。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;lt;p&amp;gt;小雞&amp;lt;/p&amp;gt;
&lt;/pre&gt;
&lt;p&gt;假設有一種情況，需要知道「小雞」屬於什麼標籤的內文，該怎麼辦呢？不能單純用角括號「&amp;lt;」和「&amp;gt;」來判斷，因為裡頭其實也可以包含其他的標籤，好比：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;lt;p&amp;gt;這是一隻&amp;lt;strong&amp;gt;3000&amp;lt;/strong&amp;gt;公斤重的小雞&amp;lt;/p&amp;gt;
&lt;/pre&gt;
&lt;p&gt;幸好，用小括號截取出來的字串，也可以當成規則的一部分。我們可以用小括號配合 \1 解決。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;lt;(\w+)&amp;gt;.*小雞.*&amp;lt;/\1&amp;gt;
&lt;/pre&gt;
&lt;p&gt;\1 代表第一個小括號截取出來的內容，在這個例子中就代表「p」。以此類推，如果有第二個括號，那麼就是 \2，第三、四個則是 \3 和 \4。&lt;/p&gt;
&lt;p&gt;原則上，regex 比較常用的規則大約都不會超出這些，看起來好像很簡單，但事實上有不少人，可能一聽到 regex，心裡就會抽蓄，不能自已。&lt;/p&gt;
&lt;p&gt;不過這也不能怪他們，舉個例子，下面是我 Google 到驗證 Email 格式的 regex：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
((([\t ]*\r\n)?[\t ]+)?[-!#-&#39;*+/-9=?A-Z^-~]+(\.[-!#-&#39;*+/-9=?A-Z^-~]+)*(([\t ]*\r\n)?[\t ]+)?|(([\t ]*\r\n)?[\t ]+)?&amp;quot;(((([\t ]*\r\n)?[\t ]+)?([]!#-[^-~]|(\\[\t -~])))+(([\t ]*\r\n)?[\t ]+)?|(([\t ]*\r\n)?[\t ]+)?)&amp;quot;(([\t ]*\r\n)?[\t ]+)?)&amp;#64;((([\t ]*\r\n)?[\t ]+)?[-!#-&#39;*+/-9=?A-Z^-~]+(\.[-!#-&#39;*+/-9=?A-Z^-~]+)*(([\t ]*\r\n)?[\t ]+)?|(([\t ]*\r\n)?[\t ]+)?\[((([\t ]*\r\n)?[\t ]+)?[!-Z^-~])*(([\t ]*\r\n)?[\t ]+)?](([\t ]*\r\n)?[\t ]+)?)
&lt;/pre&gt;
&lt;p&gt;(傳送門： &lt;a class=&quot;reference external&quot; href=&quot;http://stackoverflow.com/questions/13992403/regex-validation-of-email-addresses-according-to-rfc5321-rfc5322&quot;&gt;http://stackoverflow.com/questions/13992403/regex-validation-of-email-addresses-according-to-rfc5321-rfc5322&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;看到這個噁心的語法，誰都會想叫媽媽。&lt;/p&gt;
&lt;p&gt;但其實雖然 regex 看起來不容易，但實際去寫的時候，卻常會發現並沒有想像中困難。因為 regex 在大部分的情況下都是前後文無關的，後面不會影響前面，所以寫起來感覺像是一層一層疊起來的感覺，實際上考慮的東西並不會如何複雜。&lt;/p&gt;
&lt;p&gt;寫的時候雖然很輕鬆，但把最後結果拿給別人看的時候，就會是「一整坨」regex，別人自然會覺得它很噁心，好像很難的樣子。&lt;/p&gt;
&lt;p&gt;對此，我只能說－－反正寫出來能用就好，痛苦是之後看的人！&lt;/p&gt;
&lt;p&gt;呵呵。&lt;/p&gt;
&lt;p&gt;(開玩笑的，別當真。)&lt;/p&gt;
&lt;p&gt;我們也可以透過一些工具，像是 &lt;a class=&quot;reference external&quot; href=&quot;http://rubular.com/&quot;&gt;Rubular&lt;/a&gt; 這些線上工具，用視覺化的形式即時驗證自己寫的東西是否正確，讓我們更輕鬆地撰寫 regex。&lt;/p&gt;
&lt;p&gt;咦！ Rubular 是 Ruby 的？這怎麼可以！趕緊推薦另一個工具 &lt;a class=&quot;reference external&quot; href=&quot;http://www.regexr.com/&quot;&gt;RegExr&lt;/a&gt; ，這個工具還附帶一些用法的教學、隨附語法簡表、常用 regex 範例、甚至還開放原始碼，這真是太棒了！比前者好用一萬倍啊！&lt;/p&gt;
&lt;p&gt;(而且不是 Ruby)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;python-re&quot;&gt;
&lt;h2&gt;Python 的 re 模組&lt;/h2&gt;
&lt;p&gt;在 Python 中，要使用 regex 並不難，已經內建在標準庫裡頭了，只要引入「re」模組即可。其中最常用的函式，大概就是 re.search 函式了。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;re&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 要搜尋的內文&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;…… 小雞 30 公克重 ……&amp;quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 第一個參數代表 pattern，後者代表內文&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞 (\d+) 公克重&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 如果要抓到，就會回傳一個 Match Object，若無則回傳 None&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# group 函式會回傳截取的內容，0 代表自己， 1 代表第一個截&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 取的內容，依此類推&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# &amp;#39;小雞 30 公克重&amp;#39;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 30&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;要同時找多個符合的結果，則可以使用 re.findall 函式：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;re&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞 白雞 黃雞 綠雞&amp;quot;&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;findall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;.雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# [&amp;#39;小雞&amp;#39;, &amp;#39;白雞&amp;#39;, &amp;#39;黃雞&amp;#39;, &amp;#39;綠雞&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;若需要多次同一 regex 搜尋，可以使用 re.compile 函式預先處理 regex 以增加效能。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;re&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞 白雞 黃雞 綠雞&amp;quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;regex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;.雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;regex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;findall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# [&amp;#39;小雞&amp;#39;, &amp;#39;白雞&amp;#39;, &amp;#39;黃雞&amp;#39;, &amp;#39;綠雞&amp;#39;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;黑雞 青雞 灰雞 橙雞&amp;quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;regex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;findall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# [&amp;#39;黑雞&amp;#39;, &amp;#39;青雞&amp;#39;, &amp;#39;灰雞&amp;#39;, &amp;#39;橙雞&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;由於 regex 相當有用，所以無論是透過第三方函式庫，如 C++ 的 Qt、boost 等或是語言內建，像是 Perl、Javascript、C++11 等，現代的程式語言幾乎都能找到辦法使用 regex，舉幾個例子：&lt;/p&gt;
&lt;p&gt;Qt:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;cpf&quot;&gt;&amp;lt;QCoreApplication&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;cpf&quot;&gt;&amp;lt;QRegularExpression&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;cpf&quot;&gt;&amp;lt;QDebug&amp;gt;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QString&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;…… 小雞 30 公克重 ……&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QRegularExpression&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;小雞 (\d+) 公克重&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QRegularExpressionMatch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hasMatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()){&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qDebug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;captured&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 小雞 30 公克重&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qDebug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;captured&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 30&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Boost C++:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;cpf&quot;&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;cpf&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;

&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;cpf&quot;&gt;&amp;lt;boost/regex.hpp&amp;gt;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;…… 小雞 30 公克重 ……&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;regex&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;小雞 (\d+) 公克重&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;smatch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;regex_search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 小雞 30 公克重&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 30&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Javascript:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;…… 小雞 30 公克重 ……&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/小雞 (\d+) 公克重/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 小雞 30 公克重&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 30&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-5&quot;&gt;
&lt;h2&gt;常見應用&lt;/h2&gt;
&lt;p&gt;說了這麼多 regex 的用法，我們到底可以拿 regex 做什麼用呢？&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;section-6&quot;&gt;
&lt;h3&gt;抓取特定格式的字串&lt;/h3&gt;
&lt;p&gt;抓取特定格式的字串，大概就是最常見的應用情景，通常會順帶截取字串裡的關鍵資訊。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;re&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;…… 小雞 300 公斤重 ……&amp;quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞 (\d+) 公斤重&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;好肥的小雞……開始減肥！&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在這個例子中，小雞的體重就成功的被我抓出來，之後的減肥計劃就可以順利展開了。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;有些網站會為了讓比較弱的爬蟲自爆，又或是為了節省頻寬。會在不影響網站顯示的情況下，故意讓部分的標籤的 end tag 去掉。如果單純以普通的 HTML parser 來處理這類型的網站時，爬蟲就會死給你看。&lt;/p&gt;
&lt;p&gt;為了不要讓這些網站的奸計得逞，為了我們看漫畫的權益，很多情況下，除了用專門處理的工具像是 BeautifulSoup  外，也可以直接使用 regex 來解決。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;lt;img src=&amp;quot;([^&amp;quot;]+)&amp;quot;&amp;gt;
&lt;/pre&gt;
&lt;p class=&quot;last&quot;&gt;(而且效能似乎較好)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-7&quot;&gt;
&lt;h3&gt;驗證格式&lt;/h3&gt;
&lt;p&gt;我們也可以用 regex 來驗證某段文字的格式，這也是相當常用的做法。除了剛才見到的 Email 格式，也能用來驗證時間的格式、IP 的格式、URL 的格式等。許多時候，我們其實可以不用自己實際去寫這些對應的 regex，善用 Google 搜尋，可以節省自己許多力氣(其實 &lt;a class=&quot;reference external&quot; href=&quot;http://www.regexr.com/&quot;&gt;RegExr&lt;/a&gt;  就有提供很多例子可以使用了)。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;sd&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;sd&quot;&gt;驗證是不是兩大類寫的文章&lt;/span&gt;
&lt;span class=&quot;sd&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;雞|chicken|喵&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;可能是？&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;也許不是？&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;雖然 regex 可以做的事很多，但很多時候，善用第三方才是最佳選擇。比方說驗證時間請愛用 python-dateutil&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-8&quot;&gt;
&lt;h3&gt;取代特定格式的內容&lt;/h3&gt;
&lt;p&gt;有時，我們會想替換字串裡的某些內容，像是字串中的「小雞」是換成「小肥雞」，最直覺的做法就是使用 replace 函式：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;... 小雞 ...&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;小肥雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但是我們也可能會碰到比較複雜的情況，比如說我們想修改一個腳本：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
甲說：&amp;quot;烤雞好吃&amp;quot;

乙說：&amp;quot;才怪，炸雞才好吃&amp;quot;
&lt;/pre&gt;
&lt;p&gt;我們想把腳本中的 &amp;quot;&amp;quot; 改我們比較習慣的 「」，這時單純用字串的 replace 函式，就比較不好處理了，我們可以改用 re.sub 函式解決這個問題。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;甲說：&amp;quot;烤雞好吃&amp;quot;&lt;/span&gt;

&lt;span class=&quot;s2&quot;&gt;乙說：&amp;quot;才怪，炸雞才好吃&amp;quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;：&amp;quot;([^&amp;quot;]+)&amp;quot;&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;：「\1」&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# &amp;#39;\n甲說：「烤雞好吃」\n\n乙說：「才怪，炸雞才好吃」\n&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;re.sub 函式用法相對比較複雜，第一個參數代表要修改的 regex，第二個參數是要改成的字串，第三個參數則是來源字串。其中還有一些比較進階用法，這裡就不多提了。有興趣的可以直接參考官方的文件 (&lt;a class=&quot;reference external&quot; href=&quot;https://docs.python.org/2/library/re.html#re.sub&quot;&gt;傳送門&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;token&quot;&gt;
&lt;h3&gt;拆解特定格式的 token&lt;/h3&gt;
&lt;p&gt;Python 字串有一個好用的 split 函式，可以指定的參數將字串拆成多個子字串，比如說：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞 白雞 母雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# [&amp;#39;小雞&amp;#39;, &amp;#39;白雞&amp;#39;, &amp;#39;母雞&amp;#39;]&lt;/span&gt;

&lt;span class=&quot;s2&quot;&gt;&amp;quot;綠雞:藍雞:黑雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;:&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# [&amp;#39;綠雞&amp;#39;, &amp;#39;藍雞&amp;#39;, &amp;#39;黑雞&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;另外，如果不輸入任何參數，可以直接將一系列的空白字元(包含tab、換行等) 去掉。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;灰雞      紅雞 &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;    紫雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# [&amp;#39;灰雞&amp;#39;, &amp;#39;紅雞&amp;#39;, &amp;#39;紫雞&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不過這個函式還是有一些侷限性，如果同時碰到多種不同類型的分隔符號時，就無能為力了。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;黃雞 橙雞:青雞;金雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# ?&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這時就可以使用 re.split 函式來幫助我們。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot; |:|;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;黃雞 橙雞:青雞;金雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# [&amp;#39;黃雞&amp;#39;, &amp;#39;橙雞&amp;#39;, &amp;#39;青雞&amp;#39;, &amp;#39;金雞&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不過個人覺得這算是比較少用的功能。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-9&quot;&gt;
&lt;h3&gt;編輯器上的應用&lt;/h3&gt;
&lt;p&gt;說個題外話，regex 也不只能在程式中用到，事實上，我們在「寫程式」本身，也可以用到 regex 實用的功能。現在很多編輯器，包含 Vim、Sublime Text 都可以使用，雖然語法可能會有些許的變化，但概念都是一樣的。&lt;/p&gt;
&lt;p&gt;好比說之前碰過一個例子，專案裡有關圖表的變數命名方式與同事沒有協調好，結果同時出現 piechart 和 pie_chart 兩種命名，結果經過一些「親密交流」後，終於決定用 pie_chart 來命名……碰到這種問題時，也可以用 regex 解決。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2293b0c2-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2293b0c2-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;輸下 Ctrl + h，然後確認 Regular Expression 模式有開啟(最左邊的圖示)。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2293e5b0-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2293e5b0-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;按下 Replace All。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2293628e-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2293628e-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;搞定！&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-10&quot;&gt;
&lt;h2&gt;常見問題與注意事項&lt;/h2&gt;
&lt;p&gt;前面說了這麼 regex 好用的地方，但 regex 其實也有很多陷阱需要注意。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;section-11&quot;&gt;
&lt;h3&gt;注意「/」和「\」&lt;/h3&gt;
&lt;p&gt;regex 不同的實作品，語法規則並不統一，不同實作，不同語言，甚或是同一語言不同版本都有所差別，雖然大多數常用的規則基本相同，但還是有一些地方要注意。&lt;/p&gt;
&lt;p&gt;好比說 Perl 的 regex ，「/」是用來判斷 pattern 的開始與結束，擁有特定的意涵，因此 pattern 中的 / 前面要加上 \ 跳脫，受到 Perl 的影響，幾乎所有 regex 函式庫都對「/」有特殊的判定。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&amp;lt;Name&amp;gt;([^&amp;lt;]+)&amp;lt;\/Name&amp;gt;/&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但不是每個語言都將「/」當成分隔符號，好比說 Python 即是如此。這種情況如果還要跳脫「/」其實沒什麼道理，所以 re 其實也能同時支援不跳脫「/」的情況。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;lt;Name&amp;gt;([^&amp;lt;]+)&amp;lt;\/Name&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;lt;Name&amp;gt;飛天雞&amp;lt;/Name&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# 抓得到~&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;lt;Name&amp;gt;([^&amp;lt;]+)&amp;lt;/Name&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;lt;Name&amp;gt;飛天雞&amp;lt;/Name&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 也抓得到~&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;「\」也有異曲同工之妙，在 regex 中 \d 有特殊意涵，但有些語言本身本來就會對 \ 特殊處理(如 C/C++)，可能要改成 \\d 才能運作。&lt;/p&gt;
&lt;p&gt;尤其是 Python 在這裡也比較麻煩，因為它的「\」有時會特殊處理，有時不會，搞得像女人一樣難懂。&lt;/p&gt;
&lt;p&gt;簡單來說，Python 的邏輯是－－若『\』後面接特定字元，如「n」、「t」、「\」或數字時，就會當成特殊字元，其餘則不理會，因此在 Python 中，下例代表一樣的意義：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;\d&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;d&amp;quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# True&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但這個例子便有差別：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;n&amp;quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# False&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;尤其是後者，因為除了語言層面，「\n」對 regex 本身也有意義，因此：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 找得到(語言自己判斷為特殊字元)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;n&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 也找得到(regex 函式庫判斷特殊字元)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;所以這個地方很容易讓人產生困惑，因此我會建議一致使用 raw string 來寫 regex。Python 的 raw string 不會處理任何一個 \，所以不會造成混淆。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 代表全交由 regex 函式庫判斷&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;raw string 其實也並非全然都不處理，可以試試「r&amp;quot;雞\&amp;quot;」，看會發生什麼？&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-12&quot;&gt;
&lt;h3&gt;注意 * 是貪婪的&lt;/h3&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;lt;strong&amp;gt;小雞&amp;lt;/strong&amp;gt;
&lt;/pre&gt;
&lt;p&gt;如果我想要抓取環繞「小雞」前的標籤，該怎麼做呢？&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;lt;strong&amp;gt;小雞&amp;lt;/strong&amp;gt;&amp;quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;lt;.+&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 是不是這樣寫呢？&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;事實上，這種寫法實際抓出來的會是「&amp;lt;strong&amp;gt;雞&amp;lt;/strong&amp;gt;」，而非「&amp;lt;strong&amp;gt;」，理由是 regex 搜尋的時候，會盡可能的抓越多越好，這個例子中「.+」同時符合「strong&amp;gt;小雞&amp;lt;/strong」和「strong」，但由於 regex 是「貪婪」的，所以它會選擇多的那一個。&lt;/p&gt;
&lt;p&gt;當然，我們也可以讓它不「貪婪」，那就是在「+」後加上「?」：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;lt;strong&amp;gt;小雞&amp;lt;/strong&amp;gt;&amp;quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;lt;.+?&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# &amp;lt;strong&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這個方法雖然可行，但演算法實作的原因，效能會比較差一些，其實在很多時候，我們可以改用另一種寫法來達成同樣的目的，不需要學習新的語法。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;lt;strong&amp;gt;小雞&amp;lt;/strong&amp;gt;&amp;quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;lt;[^&amp;gt;]+&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# &amp;lt;strong&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;搞定！&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-13&quot;&gt;
&lt;h3&gt;注意換行&lt;/h3&gt;
&lt;p&gt;要注意 regex 是一行一行找的，所以對於換行的處理，有幾個比較奇怪的地方，舉個例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;雞腿&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;雞心&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;雞肝&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;雞.*&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# &amp;#39;雞腿&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;雖然前面有說過「.」代表任意字元，但其實不包含換行，當碰到第一個換行時，就會停止抓取，所以最後只抓到「雞腿」就停止了。如果要讓「.」也能代表換行字元，就要加上「DOTALL」這個 flag 才行。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;雞腿&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;雞心&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;雞肝&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;雞.*&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DOTALL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# &amp;#39;雞腿\n雞心\n雞肝\n&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;「^」和「$」的情況比較像反過來，前面說過兩者分別代表文章的「開頭」和「結尾」，但有時我們可能會希望這個「開頭」或「結尾」代表的是「行」的開頭和結尾，這時可以加上「MULTILINE」這個 flag。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;雞腿&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;雞心&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;雞肝&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;findall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;^雞.*&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# []&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;findall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;^雞.*&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MULTILINE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# [&amp;#39;雞腿&amp;#39;, &amp;#39;雞心&amp;#39;, &amp;#39;雞肝&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-14&quot;&gt;
&lt;h2&gt;結語&lt;/h2&gt;
&lt;p&gt;本來還想寫一下 regex 實作相關的介紹，不過我覺得再加內容大概就沒人想看了吧？&lt;/p&gt;
&lt;p&gt;總之，這篇介紹了 regex 的簡單用法和我對 regex 應用的心得，沒有太過深入討論 regex 太多詳細的規則語法，畢竟雖然規則很多，但多數都不太常用，我覺得需要時再上網查即可。在這個網路的時代，很多時候都不需要死背硬記，畢竟人的腦袋就這麼小小一顆，何必老是欺負它呢？&lt;/p&gt;
&lt;p&gt;以上。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[筆記 - 自動化測試與 TDD 實務開發]]></title>
        <id>https://blog.marco79423.net/articles/%E7%AD%86%E8%A8%98-%E8%87%AA%E5%8B%95%E5%8C%96%E6%B8%AC%E8%A9%A6%E8%88%87-tdd-%E5%AF%A6%E5%8B%99%E9%96%8B%E7%99%BC/</id>
        <link href="https://blog.marco79423.net/articles/%E7%AD%86%E8%A8%98-%E8%87%AA%E5%8B%95%E5%8C%96%E6%B8%AC%E8%A9%A6%E8%88%87-tdd-%E5%AF%A6%E5%8B%99%E9%96%8B%E7%99%BC/"/>
        <updated>2015-06-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[第一次認識 91 這個名字是在「iT邦幫忙」鐵人賽中注意到的，這個比賽簡單來說就是一些想挑戰自己的人，連續 30 天發表技術類的文章，像瘋子一樣的比賽。之所以記得 91 這個名字，除了名字好記外，還因為他的文章內容特別充實而且有趣 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;第一次認識 91 這個名字是在「iT邦幫忙」鐵人賽中注意到的，這個比賽簡單來說就是一些想挑戰自己的人，連續 30 天發表技術類的文章，像瘋子一樣的比賽。之所以記得 91 這個名字，除了名字好記外，還因為他的文章內容特別充實而且有趣，所以一個不小心，就開始追他的文章了(羞)，雖然我自己不使用 .Net 開發，但我仍然覺得讀他的文章令我獲益良多。&lt;/p&gt;
&lt;p&gt;這次難得有機會，去參加 91 主講的「自動化測試與 TDD 實務開發」課程，反正公司出錢，自然是要盡力爭取，結果一個不小心爭取到了，所以就決定不小心的去上課。&lt;/p&gt;
&lt;p&gt;結果聽著聽著，忽有所感，覺得好像得到了什麼，突然一聲系統提示：您的等級上升一級，所以這篇心得筆記就出來了。&lt;/p&gt;
&lt;p&gt;基本上這篇文章主要的內容是他上課的內容還有一些我平常學習心得的整理。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h2&gt;為何要測試？&lt;/h2&gt;
&lt;p&gt;自動化測試最主要的目的是減少開發的時間。除了具體的開發時間外，還包含因錯誤而修改的時間，或是因需求修改而重寫的時間。&lt;/p&gt;
&lt;p&gt;我們在開發程式的過程中，時常會碰見下列問題：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;花太多時間找尋錯誤，改 A 掛 B，在改 A 的當下，並不知道會影響到 B，直到系統出事了，才只好全部分析整個系統一遍，才知道其實是 B 掛了，非常浪費時間！&lt;/li&gt;
&lt;li&gt;系統刪刪改改，改到最後到底系統的那些部分的運行是正確無誤的，其實就算是開發者自己也不敢確定。尤其是當需求改來改去時，這種問題會變得非常常見，結果因為這份不確定，時常又得花時間做確認，還是浪費時間。&lt;/li&gt;
&lt;li&gt;環境無法測試，開發者常不能接觸實際的系統，只能憑空猜。這包含許多種情形，並不單指是實際上線的系統，也可能是兩組團隊同時開發一部分的產品，因為另一組團隊的東西尚未完成，所以無法測試，還要等到另一組團隊完成後才能整合，這個過程十之八九都會出事，所以還要再花更多時間解決，都是浪費時間啊！！&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以為了避免浪費時間，結論就是要測試，測試可以減少上述的問題，增加開發的效益。正所謂好的測試能帶你上天堂，省掉許許多多的開發時間，過上不用加班的美好生活！這真是太好了！(迷之音：有夢最美，希望相隨)&lt;/p&gt;
&lt;p&gt;但是也別忘了，測試不是目的，只是為了讓開發的過程更順利而已。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h2&gt;單元測試&lt;/h2&gt;
&lt;p&gt;這裡指的測試包含多個層面，包含單元測試(Unit Test)、整合測試(Integration Test)等等，我主要會提比較有感覺，有關單元測試的部分。&lt;/p&gt;
&lt;p&gt;單元測試為最小的測試單位，不含商業邏輯，通常為測試一個函式。由於一次只會測一件事，所以無論是成功還是失敗，都能明確知道其代表的意義。&lt;/p&gt;
&lt;p&gt;反過來說，如果不這麼做的話，我們就很難直接透過測試知道那裡出現問題了，好比說：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;test_crud&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;chicken&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 新增&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;chicken&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 更新&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;chicken&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# 刪除&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert_equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;chicken&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 讀取&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果死了，究竟是那裡出錯呢？有可能是新增失敗，也可能是刪除失敗，我們無法直接看出來。&lt;/p&gt;
&lt;p&gt;單元測試的意義，在於透過模擬外部如何使用，來驗證其行為是否符合預期。我們可以在寫測試的過程中，確認功能的需求。為了讓自己寫的程式能夠被測試，就會盡可能思考怎麼去驗證這個程式，不停地改善程式的架構，不知不覺的就能減少物件之間耦合性。&lt;/p&gt;
&lt;p&gt;有了單元測試，我們再不用擔心改東改西，我們可以確保每次增加一個新的功能，都不會影響到之前的需求，就算真的影響到了，也能馬上知道那裡出問題，而且錯的地方很清楚是什麼，不會誤判。&lt;/p&gt;
&lt;p&gt;而測試案例本身，幾乎就可以拿來當成說明需求和使用方法的文件。有了它，我們就可以減少說明文件的使用，而且對 RD 來說，這樣可能反而更容易搞懂你的程式碼。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;first&quot;&gt;
&lt;h3&gt;FIRST 原則&lt;/h3&gt;
&lt;p&gt;好的單元測試應該要符合 FIRST 原則，分別是 F(Fast)、I(Independent)、R(Repeatable)、S(Self-validating) 和 T(Timely)。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;F (Fast)&lt;/p&gt;
&lt;p&gt;測試所需要時間要快，最好不要超過 500ms。如果花的時間太長，開發者就不會想測試，失去寫單元測試的意義。而且速度太慢通常代表物件有外部相依，可能需要重新審視一下物件的相依情形。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;時間的實際的長短，也有多種說法，有的說 100ms，也有的說 200ms，我認為數值不重要，應該要依實際情形而定。&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;I (Independent)&lt;/p&gt;
&lt;p&gt;為了保證其代表的意義，也為了避免誤判，單元測試最關鍵的要點是外部相依性必須為零，舉凡檔案、網路、其他類別都不能影響測試的結果。一旦包含外部的因素，好比說網路，一旦相依於網路，如果測試失敗，很難直接確定是函式實作的錯誤，還是網路的問題。一旦依賴其他類別的功能，失敗了也很難確認是自己的問題還是該類別的問題。&lt;/p&gt;
&lt;p&gt;除此之外，測試案例(TestCase) 之間的相依性也應該要為零。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;R (Repeatable)&lt;/p&gt;
&lt;p&gt;無論何時、何地，測試的結果不能改變。比如說寫一個函式，「測試今天是否為二月二十四日」，同樣的測試程式碼，不能因為不同時間測而有所差別。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;Self-validating&lt;/p&gt;
&lt;p&gt;每一個測試案例都真能驗證某一件事，如果對就是對，錯真的會回傳錯，而這個過程不應該需要手動操作，才能判斷結果是否正確。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;T (Timely&lt;/p&gt;
&lt;p&gt;程式碼與測試程式碼要即時，只有兩者都完成才算完成。不要相信測試程式碼有可能之後會補齊，那是不可能發生的。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了基本的 FIRST 原則，實際撰寫測試時，也要非常注意測試程式碼的可讀性。因為別人很可能會直接透過測試程式來了解你的程式的功能和用法，以及是否滿足自己的需求。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;a&quot;&gt;
&lt;h3&gt;3A 原則&lt;/h3&gt;
&lt;p&gt;為了增加測試程式碼的可讀性，可以使用 3A 原則的格式來寫測試程式碼。&lt;/p&gt;
&lt;p&gt;3A 分別代表：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;Arrange(初始、期望結果)：初始化目標物件和相依物件，設定物件之間的互動方式，並指定功能執行後的預期結果。&lt;/li&gt;
&lt;li&gt;Act(實際呼叫)：執行待測試的功能。&lt;/li&gt;
&lt;li&gt;Assert(驗證)：驗證結果是否符合預期。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;實際寫的樣子約略如下：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;test_Calc_add_first_1_second_2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Arrange&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Calc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;second&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Act&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;second&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Assert&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert_equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;開頭是 Arrange，再來是 Act，最後則是 Assert 的部分，使用 3A 原則的格式撰寫測試，好處是只要了解該原則的人，就可以輕易看懂你的程式碼。&lt;/p&gt;
&lt;p&gt;除此之外，一致的命名方式也可以進一步增加程式的可讀性。好比說待測的物件固定以 target 命名，或是期望的結果固定以 expected 命名等。&lt;/p&gt;
&lt;p&gt;測試函式的命名以一樣容易理解為主，有時可以不用太在乎英文的文法，比較極端的例子，如果團隊都是台灣人，甚至可以考慮直接選擇用中文來命名函式。(迷之音：「畢竟人家 yahoo 的還不是用中文寫測試？」)&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;test_Calc_add_參數給字串會回傳0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Arrange&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Calc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;second&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;中文&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;字串&amp;quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Act&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;second&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Assert&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert_equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但撰寫的格式，也不是說非要如此不可，只要有助於別人理解程式碼，並沒有規定一定要用上面例子的方式寫測試才行。&lt;/p&gt;
&lt;p&gt;另外，3A 中的 Assert 也是很重要的一環，有時也是容易被忽略的一環，根據 FIRST 原則中的 Self-validating，「正確的出錯」也是不可或缺的。不是綠燈就好，要能正確地紅燈才是關鍵。你可以故意讓結果出錯，看看測試程式是否真能把這個錯誤抓出來。&lt;/p&gt;
&lt;p&gt;驗證結果是否正確的情況有很多，除了最常見的測試回傳值是否符合預期外，也可以測試狀態的改變或是用 Mock 測試與外部的互動。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;可驗證的內容&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;回傳值（最常見)&lt;/li&gt;
&lt;li&gt;狀態&lt;/li&gt;
&lt;li&gt;與外部的互動(Mock)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;單元測試最重要的概念，我認為恐怕就是「隔離」了，也就是 FIRST 原則 I 的部分。&lt;/p&gt;
&lt;p&gt;為什麼說「隔離」最重要？&lt;/p&gt;
&lt;p&gt;我們可以反過來看如果沒有做好隔離，會有什麼問題？&lt;/p&gt;
&lt;p&gt;沒有做好隔離的程式碼，因為每次都要用到多種功能，甚至可能要與外部溝通，因為執行測試的時間會長許多，也會違反 FIRST 的 Fast 原則。&lt;/p&gt;
&lt;p&gt;平常上網，連一秒延遲都受不了，如果每次執行測試的時間太長，就不能養成「測試強迫症」的好習慣(咦？)。隨著所需要的測試時間越來越長，很多人最後就會放棄使用了。&lt;/p&gt;
&lt;p&gt;沒有做好隔離的程式碼，互相相依，一旦發生問題，很難準確的判斷錯誤的原因何在。因為算出來的結果錯了，原因可能很多，也許是因為抓取資料的時候抓錯了，也許是確實是計算時出錯了，但說不定也可能結果是對的，只是輸出的格式錯了而已。我們可能會因為誤判錯誤的地方，浪費太多時間在除錯上。&lt;/p&gt;
&lt;p&gt;所以說如果測試不能準確告訴我們那裡錯了，很多時候其實就失去測試的意義。&lt;/p&gt;
&lt;p&gt;還有一個問題，商業程式通常都不只有一個人開發，這時候還得找出那個環節出錯才行。這更加麻煩，由於對別人的程式碼的不熟悉，再加上人們都「傾向」認為不是自己的錯，而是別人的錯，所以還必須花時間「證明」是誰的錯。無形中，時間就這樣又被浪費掉了。&lt;/p&gt;
&lt;p&gt;除此之外，由於多人平行開發，所以在開發的過程中，時常會碰到別人開發的部件可能還沒完成的情況，既然沒完成，又怎麼能測試呢？&lt;/p&gt;
&lt;p&gt;所以為什麼要隔離？透過拆分不同物件的功能，讓每一個物件只負責一件事，盡可能的減少對別人的依賴，讓別的程式碼不會影響到這個類別。這樣不但可以減少錯誤發生，可以讓測試的執行速度變快，還能增加類別重用的可能性。&lt;/p&gt;
&lt;p&gt;但物件還是要相互溝通，不可能真的完全無關，這時就可以利用一些物件導向的技巧來解決這個問題，像是依賴介面、依賴注入等技巧解決。詳細的技巧可以使用 Google 查詢，或是直接看 91 的部落格(推銷？)。接下來，透過實作其介面的方式製作假物件，模擬物件行為，然後再將這些假物件傳給目標，確認其行為是否合乎預期，以達成測試的目的。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;為何要隔離？&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;執行速度快&lt;/li&gt;
&lt;li&gt;關注點分離&lt;/li&gt;
&lt;li&gt;單一職責&lt;/li&gt;
&lt;li&gt;可以獨立測試&lt;/li&gt;
&lt;li&gt;健壯性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如何解決相依？&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;關注點分離&lt;/li&gt;
&lt;li&gt;單一職責&lt;/li&gt;
&lt;li&gt;依賴介面&lt;/li&gt;
&lt;li&gt;依賴注入&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;要實行單元測試，不光是加上測試程式碼而已，設計的方式也必須要有所改變才行，另一方面，大部分舊的程式碼，就只是為了寫出功能，沒有考慮如何測試。這麼一來，前人留下的債，後面的人改得就很辛苦。問題是後面的人為什麼要幫前人改？有什麼動力幫前人改？又不會加薪？就我看來，這些就是 TDD 實務上時常會覺得難以實現的原因。&lt;/p&gt;
&lt;p&gt;不過話雖如此，前人寫的程式碼就算了，我認為自己的程式碼還是要有所要求才行。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;code-coverage&quot;&gt;
&lt;h3&gt;程式碼覆蓋率(Code coverage)&lt;/h3&gt;
&lt;p&gt;如果程式沒有被測試保護，一旦發生改動，就不能保證最後結果是否為正確。在寫測試的過程中，可以用程式碼覆蓋率這個指標來保證測試的品質。&lt;/p&gt;
&lt;p&gt;測試的覆蓋率若為 100%，代表自己的程式都有被測試保護，任何一個改動都可以非常安心。&lt;/p&gt;
&lt;p&gt;反過來說，如果覆蓋率不足，就代表可能測試案例(TestCase)不足夠，有該測的東西沒有測。碰到這種情況就該增加一些測試案例來保證行為，除非它真的很不重要。&lt;/p&gt;
&lt;p&gt;話說回來，如果真的不重要的到不需要寫測試，可能也代表另一狀況，那就是這段程式碼可能與需求無關，這時就可以選擇直接刪去這段無用的程式碼。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;Code Coverage 不足的意義&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;測試案例不足&lt;/li&gt;
&lt;li&gt;存在與需求無關的程式碼&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;程式碼覆蓋率是一個非常實用的指標，也是非常不實用的指標。大家都知道這個數字越高越好，但你不用指望前人就有這個意識，所以一開始大家都是零。老闆看到這個數字可能就會說：「這什麼指標？」「越高越好？」「最高是多少？」「100%？」「那你說為什麼我們不是 100%？明天內解決！」&lt;/p&gt;
&lt;p&gt;Fu*k！&lt;/p&gt;
&lt;p&gt;結果大家都不敢導入這項指標(至少不敢讓老闆知道)。&lt;/p&gt;
&lt;p&gt;所以一個重要的觀念是這個指標千萬不能急著拿來當 KPI，畢竟這包含了很多層面的因素在裡頭。不過雖然在實務上要求完美的 100% 可能沒法這麼快，但也不能因為這樣就不用。&lt;/p&gt;
&lt;p&gt;反正雖然不能保證「數字」，但還是可以「趨勢」嘛！只要這個數字持續上升，那也就表示新寫的程式碼確實都有做好測試不是嗎？只要保持下去，之前的程式碼影響會越來越小，覆蓋率的數字總會越來越漂亮的，所以關鍵是只需要確保數值不可以下降就行了。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;檢查測試案例有沒有包含最主要的情境(尤其是線上回報的)&lt;/li&gt;
&lt;li&gt;檢查有沒有不必要的程式碼和測試項目&lt;/li&gt;
&lt;li&gt;數字不是絕對，只要保持上升即可&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-3&quot;&gt;
&lt;h3&gt;整合測試&lt;/h3&gt;
&lt;p&gt;原則上，不是單元測試，幾乎都可以說是整合測試，不過在實務的情況下，整合測試(Integration Test)，至少至少針對某一個類別的測試，常與單元測試幾乎沒什麼不同，畢竟無論是單元測試還是整合測試，需求都不會改變。&lt;/p&gt;
&lt;p&gt;要說最直觀的差別，可能就是單元測試相依的部分是透過 Stub/Mock 來模擬，而整合測試則不用，所以可以很明顯的看出來單元測試的測試案例中，通常都會有相依物件的建立和注入，但整合測試就沒有。&lt;/p&gt;
&lt;p&gt;不過整合測試也可以是更高一層，針對 module/package 的測試，這種就是黑箱測試了，不需要知道內部的相依性和實作，只要知道輸入和輸出就可以做驗證。&lt;/p&gt;
&lt;p&gt;事實上，若要再分，還是有更高粒度的測試，也就是這種就是 Acceptance test，讓使用者來驗收測試，以使用者的角度來看需求是否有如預期地被完成。這種測試常常會有UI，算是最貼近使用者的測試。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;tdd-1&quot;&gt;
&lt;h2&gt;測試驅動開發(TDD)&lt;/h2&gt;
&lt;p&gt;測試驅動開發，它開發的流程其實一直都很清楚，很多人也耳熟能詳，簡單來說就是先寫測試，然後才寫實作。&lt;/p&gt;
&lt;p&gt;實作不用寫得太複雜，只要剛好可以通過測試即可，一步一步來，每一行程式碼都只為了滿足需求，用最笨的實作完成測試，不斷重覆這個流程，直到出現那種「這種程式碼讓人難以忍受的感覺」為止，好比說當你發現某段重覆或類似的程式碼出現過三次，這時就應該要重構了。等到重構完後，再開始進行新的一輪新功能實作。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;三次法則&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;重覆的程式碼最多只能出現二次，因為一旦出現三次，往往就會有第四份、第五份，所以重構是必要的。反過來說，之所以可以允許有兩份，常是因為實務上的考量，畢竟會發生這種事情，有可能是很緊急的情況。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;這種開發流程的改變，還有另一個好處－－如果說平常寫程式是腦袋已有了一個小雞，只是花時間把它寫出來，那麼 TDD 就是腦袋放不下小雞的人的福音。你可以不用放下一整隻雞才能開始寫，只要想到一隻雞腳就可以開工了。當寫完一隻雞腳，你可能就會發現寫另一隻雞腳變得如此簡單，不斷如此反覆，不知不覺，一整隻雞就這麼寫完了。(然後吃掉！)&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;用最笨的方式開發，千萬不要忍不住多實作，這樣才能減少過度的設計。而且習慣了 TDD，萬一寫了新的測試卻沒出現紅燈，很可能反而會嚇到自己(咦？&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;基本上，只要依循 TDD 的流程開發，每一行程式碼就都會有測試保護，因此完全不用擔心重構會出問題，而每次需求的變動，也不用擔心會不會把之前的程式碼改壞，因為測試都會乖乖「慘叫給你看」。既然不會發生問題，就很容易「忍不住」想要將程式碼改得更好看，所以整個程式架構就會越來越漂亮。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;TDD 的原則&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;只寫剛好可以通過測試的程式碼&lt;/li&gt;
&lt;li&gt;不能在測試不過的情況下加新的測試&lt;/li&gt;
&lt;li&gt;新加的程式碼只允許剛好讓先前不過的測試通過&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;tdd-2&quot;&gt;
&lt;h3&gt;使用 TDD 重構前人的程式碼&lt;/h3&gt;
&lt;p&gt;實務上要導入 TDD，免不了必須要處理前人的程式碼，這裡 91 已經將相關的技巧整理的非常好，我覺得這甚至是他在有關 TDD 的介紹最精華的部分，他用宅配的例子講解，非常精彩，有興趣的可以直接去看他的文章(&lt;a class=&quot;reference external&quot; href=&quot;http://www.dotblogs.com.tw/hatelove/archive/2013/01/11/learning-tdd-in-30-days-catalog-and-reference.aspx&quot;&gt;傳送門&lt;/a&gt;)，反正我也不會寫得比他好。&lt;/p&gt;
&lt;p&gt;不過整理一下，大致上有下列幾個步驟：&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;先用測試保護整個程式(雖然各別的物件可能不好測，但整個程式還是可以測的)&lt;/li&gt;
&lt;li&gt;為程式加上適當的註解(用人類的語言描述程式碼在做什麼事)&lt;/li&gt;
&lt;li&gt;將程式的 UI 與程式邏輯分開&lt;/li&gt;
&lt;li&gt;將各個物件的職責分開，學著用各個物件的角度看世界，不是我的不該我做&lt;/li&gt;
&lt;li&gt;再針對這些物件的行為建立單元測試&lt;/li&gt;
&lt;li&gt;擷取共同之處抽出介面，最後再將生成物件的職責獨立出來。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;關鍵是每一次重構都必須要有測試保護，不斷小範圍的重構會比一次大範圍的重構有效果，而且一次只做一件事，切勿邊重構邊加需求。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;tdd-3&quot;&gt;
&lt;h3&gt;TDD 的好處和意義&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;&lt;strong&gt;減少過度設計(Over-engineering)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;很多人都有過度設計的問題，尤其是那些學太多設計模式(Design Pattern)的人來說，常常會忍不住會想套用，最後寫了一堆「看起來很有彈性」但沒用的程式碼。&lt;/p&gt;
&lt;p&gt;但是 TDD 每一次循環，都是只寫剛好能滿足需求的程式碼，每一行程式碼都是為了需求而產生，所以永遠不用擔心過度設計的問題。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;&lt;strong&gt;改善 API 的設計與可用性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TDD 最大的特色就是先寫測試再寫實作，也就是說，再實作之前，就必須先考慮該如何使用。很多時候，實作者和使用者的想法差別是很大的，很多時候，實作者所謂的「我覺得這樣這樣很好用」與使用者的想法大相逕庭，這是看事情角度的問題，不過現在因為必須先寫測試，因此實作者便被迫以使用者的觀點思考。&lt;/p&gt;
&lt;p&gt;畢竟，不使用的話，怎麼知道設計對不對？&lt;/p&gt;
&lt;p&gt;TDD 可以改變實作者看問題的角度，由於測試先行，還沒有實作，就比較容易能以使用者的角度來看問題，這樣設計出來的 API 可用性就會比較好。至少，只要你有辦法寫完測試，你就能保證只要而將 API 實作出來，這個功能絕對是可用的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;&lt;strong&gt;減少維護說明文件的需求&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有人說，RD 最痛恨兩件事，一是寫註解和說明文件，二是別人不寫註解和說明文件。&lt;/p&gt;
&lt;p&gt;程式畢竟不是一個人開發的，為了互相溝通，還是必須要有一個交流的方式。與其要求有交流障礙的 RD 說明他的程式，看起他吱吱嗚嗚，詞不達意的發言，直接看他的測試可能還比較快。至少對 RD 來說，這可能反而比看說明文件還來得容易。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;&lt;strong&gt;減少開發和思考的難度&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;俗話說，「萬事起頭難」，程式的問題通常很複雜，很難一開始就用全局的思考來寫程式，時常會找不到切入點。TDD 一開始都是用最簡單的案例開始，一步一步處理越來越複雜的案例。由於只單走一條路，事情就不會這麼複雜，當走通了一條路，之後再拓寬便容易許多，開發的過程就會變得比較順暢。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class=&quot;first&quot;&gt;&lt;strong&gt;增加程式碼的品質和確保其正確性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由於測試先行，所以在確定能不能用之前，就要先知道東西能不能測，如果發現不能測，就會知道職責分配的方式有問題。這時就能重新考慮程式設計的架構，進而達到改善品質的效果。&lt;/p&gt;
&lt;p&gt;有了測試，不用擔心改東壞西的問題，因為每一個需求都有測試保護，如果不小心影響到別的功能，也能馬上知道。如果沒有測試保護，你很難保證你改的東西一定不會有問題。所以最後就會變成大家都不敢去改動，不願意去重構，不適合的程式架構一直保留，隨著程式的增長，程式碼的品質就會越來越糟。&lt;/p&gt;
&lt;p&gt;你可以透過這些測試，知道你目前完成的內容有那些。而且因為有測試保護，你可以證明有測試涵蓋到的範圍都是正確的，你可以很有信心。如果之後發現了什麼問題，你只要補上相應的測試並讓它變成綠燈，就能證明你已經解決這個問題。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;TDD 的意義和好處(or 說服老闆的理由？)&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;減少過度設計&lt;/li&gt;
&lt;li&gt;改善 API 的設計與可用性&lt;/li&gt;
&lt;li&gt;減少維護說明文件的需求&lt;/li&gt;
&lt;li&gt;減少開發和思考的難度&lt;/li&gt;
&lt;li&gt;增加程式碼的品質和確保其正確性&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;bdd&quot;&gt;
&lt;h2&gt;更進一步 - BDD&lt;/h2&gt;
&lt;p&gt;BDD 全名為老闆意向驅動開發(Boss-Driven Development)，全世界最常見的開發方式，一切都是以老闆的意向開發，不過由於這種開發方式非常依賴老闆的腦袋，所以世界上才有這麼多失敗的產品……咦？好吧，至少「理想上」的 BDD 指的是行為驅動開發(Behavior-Driven Development)。&lt;/p&gt;
&lt;p&gt;BDD 可說是進一步改善 TDD 的缺點的好物。&lt;/p&gt;
&lt;p&gt;前面說了 TDD 這麼多優點，其實關鍵就 TDD 是讓開發的過程中更重視需求，需求要什麼，才做什麼。最理想情況就是需求與程式完全一致，才是最完美的。為了讓需求與程式是一致的，我們可以使用測試案例來比對需求是否一致。&lt;/p&gt;
&lt;p&gt;概念好棒棒，但有一個小問題－－無論測試案例寫得再好、再乾淨，但除了 RD，其他人都還是看不懂測試案例。要怎麼知道需求與程式的一致的？結果搞了半天，還是要準備一個落落長的文件來互相溝通。&lt;/p&gt;
&lt;p&gt;我覺得 BDD 最大的好處便是能夠用人話來表達需求，讓 RD 以外的人看得懂，然後再透過工具轉換成可以使用的測試，讓 RD 自己看得懂。&lt;/p&gt;
&lt;p&gt;沒錯，就是「翻譯蒟蒻」！代溝處理工具！&lt;/p&gt;
&lt;p&gt;概念很好，但我覺得這種方式最關鍵的地方是工具，並且需要與好的 IDE 互相配合才能實現，所需要的觀念較少。好的工具可以讓需求可以用更清晰的表達，像是自動產生美美的圖表等，反過來也可以轉成非常實用的測試樣版，減少 RD 撰寫測試的時間，並且可以輕易的除錯等。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-4&quot;&gt;
&lt;h2&gt;總結與心得&lt;/h2&gt;
&lt;p&gt;這些技術都有一個很簡單的目的，那就是滿足需求，讓程式確實與需求一致。並且盡可能的在這個目標下追求效率。在這個過程中，我們發現測試可以幫助我們達成這個目的，好的測試幾乎可以完美解決我們開發上碰到的諸多問題。但測試也有它的難處，畢竟它還是需要另外寫很多看似無用的程式碼，還是會花上不少時間，這免不了會讓人產生惰性，懶得去寫它。因此這時一個好的 IDE 可以幫助我們處理大部分無聊的工作，減少所需的時間，讓我們專注在開發上。&lt;/p&gt;
&lt;p&gt;我覺得這是一個非常充實的 21 小時的課程，雖然我不會 C#，也不熟 Visual Studio，課上得很辛苦，但我能說 91 的講解絕對不會比他部落格文章差，真的可以給個讚。&lt;/p&gt;
&lt;p&gt;俗話說得好：「好課當修直需修」，有興趣可以去 &lt;a class=&quot;reference external&quot; href=&quot;http://skilltree.my/&quot;&gt;SkillTree&lt;/a&gt; 關注一下。&lt;/p&gt;
&lt;p&gt;最後是自己的期許，「盡可能的讓自己的程式碼都被測試覆蓋，沒有測試的程式碼都不算完成」&lt;/p&gt;
&lt;p&gt;以上。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[俗辣蟲漫畫下載器 CSsulaBug]]></title>
        <id>https://blog.marco79423.net/articles/%E4%BF%97%E8%BE%A3%E8%9F%B2%E6%BC%AB%E7%95%AB%E4%B8%8B%E8%BC%89%E5%99%A8-cssulabug/</id>
        <link href="https://blog.marco79423.net/articles/%E4%BF%97%E8%BE%A3%E8%9F%B2%E6%BC%AB%E7%95%AB%E4%B8%8B%E8%BC%89%E5%99%A8-cssulabug/"/>
        <updated>2015-02-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[






名稱
俗辣蟲漫畫下載器 CSsulaBug

使用語言
C++(Qt 5.4.0)

使用平台
Windows, MAC

版本資訊
1.2 ...]]></summary>
        <content type="html"><![CDATA[&lt;img alt=&quot;https://blog.marco79423.net/backend/static/25d4e896-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/25d4e896-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;12%&quot; /&gt;
&lt;col width=&quot;88%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;名稱&lt;/td&gt;
&lt;td&gt;俗辣蟲漫畫下載器 CSsulaBug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用語言&lt;/td&gt;
&lt;td&gt;C++(Qt 5.4.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用平台&lt;/td&gt;
&lt;td&gt;Windows, MAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;版本資訊&lt;/td&gt;
&lt;td&gt;1.2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;作者&lt;/td&gt;
&lt;td&gt;兩大類&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;原始碼&lt;/td&gt;
&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://github.com/marco79423/CSsulaBug&quot;&gt;GitHub&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;下載連結&lt;/td&gt;
&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://blog.marco79423.net/backend/static/25da0ea2-2cd0-11f1-bbd2-32abd871d697/&quot;&gt;Windows 版載點&lt;/a&gt; , &lt;a class=&quot;reference external&quot; href=&quot;https://blog.marco79423.net/backend/static/25d56b22-2cd0-11f1-bbd2-32abd871d697/&quot;&gt;MAC 版載點&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;!-- *若用 Chrome 下載時出現「下載內容不正常，可能會有危險」是正常現象，點選保留即可。(因為 Chrome 會對少見、又沒「認證」的程式來源提出警告，而我剛寫的新玩意兒自然是挺少見的……)* --&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;1.2.0 版更新內容&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;新增「我的下載」頁面&lt;/li&gt;
&lt;li&gt;新增支援下載隊列&lt;/li&gt;
&lt;li&gt;改善下載的速度和品質&lt;/li&gt;
&lt;li&gt;刪去漫畫分類篩選的功能&lt;/li&gt;
&lt;li&gt;改進整個程式架構&lt;/li&gt;
&lt;li&gt;更新成 Qt 5.4.0&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1.1.0 版更新內容&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;新增支援 耽美漫畫(&lt;a class=&quot;reference external&quot; href=&quot;http://blmanhua.com/&quot;&gt;http://blmanhua.com/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;更新介面並新增漫畫分類&lt;/li&gt;
&lt;li&gt;更新成 Qt 5.3.2&lt;/li&gt;
&lt;li&gt;解決更新漫畫資訊可能不全的問題&lt;/li&gt;
&lt;li&gt;讓漫畫名字可以被反白複製&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1.0.0 版更新內容&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;改名為俗辣蟲漫畫下載器&lt;/li&gt;
&lt;li&gt;重新撰寫所有程式碼&lt;/li&gt;
&lt;li&gt;重新設計整個程式介面&lt;/li&gt;
&lt;li&gt;若目標路徑某一話已經存在，不會重覆下載&lt;/li&gt;
&lt;li&gt;新增直接下載某一話的功能&lt;/li&gt;
&lt;li&gt;現在更新漫畫資料和下載時畫面不會卡死&lt;/li&gt;
&lt;li&gt;解決之前偶爾會被漫畫網站阻檔的問題&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;0.2.1 版更新內容&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;修正網站改版後不能下載的問題&lt;/li&gt;
&lt;li&gt;新增 MAC 版(因為老子有 MAC 啦~)&lt;/li&gt;
&lt;li&gt;從 Qt4 更新為 Qt5&lt;/li&gt;
&lt;li&gt;改善程式介面設計&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;簡介&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;「俗辣蟲漫畫下載器漫畫下載器」是跨 PC 和 MAC 的漫畫下載軟體，目標是提供輕鬆簡易的漫畫下載環境。&lt;/p&gt;
&lt;p&gt;想要下載漫畫？只要三步驟：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;打開下載器&lt;/li&gt;
&lt;li&gt;搜尋想要下載的漫畫&lt;/li&gt;
&lt;li&gt;點選下載&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;就這麼簡單！&lt;/p&gt;
&lt;p&gt;下載器會自動接手之後的動作，去網站抓取漫畫，然後下載至桌面上。雖然來源是大陸網站，但程式會自動繁簡轉換，可以直接用繁體字搜尋。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/25d531a2-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/25d531a2-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/25d45f98-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/25d45f98-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/25d4b52e-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/25d4b52e-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;支援的漫畫來源&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;36%&quot; /&gt;
&lt;col width=&quot;64%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;SF 互動傳媒網&lt;/td&gt;
&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;http://comic.sfacg.com&quot;&gt;http://comic.sfacg.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;耽美漫畫&lt;/td&gt;
&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;http://blmanhua.com&quot;&gt;http://blmanhua.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;本下載器僅為測試用途，下載的漫畫請於24小時內刪除。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 Python 的生活日常系列 [1] - 圖片整理]]></title>
        <id>https://blog.marco79423.net/articles/%E4%BD%BF%E7%94%A8-python-%E7%9A%84%E7%94%9F%E6%B4%BB%E6%97%A5%E5%B8%B8%E7%B3%BB%E5%88%97-1-%E5%9C%96%E7%89%87%E6%95%B4%E7%90%86/</id>
        <link href="https://blog.marco79423.net/articles/%E4%BD%BF%E7%94%A8-python-%E7%9A%84%E7%94%9F%E6%B4%BB%E6%97%A5%E5%B8%B8%E7%B3%BB%E5%88%97-1-%E5%9C%96%E7%89%87%E6%95%B4%E7%90%86/"/>
        <updated>2015-02-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[
前言
不知道你有沒有覺得花了很多時間在學寫程式，卻不知道能用來做什麼的感覺？
據我所知，許多國高中甚至已經將寫程式帶入課程之中了。但學了半天，又有多少人會去實際使用呢？感覺就像學了另一門用不著的外語一樣。以為寫程式只是用於作業而已。
即使是本科生，也有很多人覺得那不過是畢業後在職場上寫那些無聊的大程式用的。好玩的程式？厲害的程式？那是只有智商不屬於人類範疇的生物才寫的出來的東西，既然自己只是普通人 ...]]></summary>
        <content type="html"><![CDATA[&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;不知道你有沒有覺得花了很多時間在學寫程式，卻不知道能用來做什麼的感覺？&lt;/p&gt;
&lt;p&gt;據我所知，許多國高中甚至已經將寫程式帶入課程之中了。但學了半天，又有多少人會去實際使用呢？感覺就像學了另一門用不著的外語一樣。以為寫程式只是用於作業而已。&lt;/p&gt;
&lt;p&gt;即使是本科生，也有很多人覺得那不過是畢業後在職場上寫那些無聊的大程式用的。好玩的程式？厲害的程式？那是只有智商不屬於人類範疇的生物才寫的出來的東西，既然自己只是普通人，又何必強求？&lt;/p&gt;
&lt;p&gt;「我太弱了，只會寫 Hello World，那種很厲害的東西寫不出來啦！」&lt;/p&gt;
&lt;p&gt;有些人甚至認為－－那些 ACM 高手就是程式人的範本，以為不斷地敲腦袋，想出「嚇死人不償命」的演算法，就是寫程式的一切。&lt;/p&gt;
&lt;p&gt;想太多！&lt;/p&gt;
&lt;p&gt;程式是為了方便人們生活的工具，學寫程式是讓我們自己有能力自己方便自己的生活。&lt;/p&gt;
&lt;p&gt;請問我們日常生活中，有一直在做什麼驚天動地、嚇死人不償命的事嗎？隨手投資千億股票、隨口一句千萬人頭落地？至少我沒有，我只是個普通人，所以自然不需要嚇死人的程式能力才能使我的生活更方便。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h2&gt;讓寫程式融入日常生活中&lt;/h2&gt;
&lt;p&gt;何謂日常生活？&lt;/p&gt;
&lt;p&gt;日常生活就是一堆無聊瑣事的集合，沒有什麼難度，但是很麻煩，很無聊，而且很花時間。好比說老闆隨口一句：「嗯，那就把投影片的別人公司的 logo 去掉，換成自己的吧。你『順便』做一下」。&lt;/p&gt;
&lt;p&gt;所以當老闆回辦公室泡茶後，你就得花了數個小時，一張張修改、重拉投影片。這時隔壁的同學就會適時的出現，拍拍你的肩說：「兄弟，這就是『生活』呀。」&lt;/p&gt;
&lt;p&gt;談功課太殘忍，先來談談休閒生活。&lt;/p&gt;
&lt;p&gt;但自己的休閒生活很多時候也沒多有趣，今天要來討論的是很多人生活中常會出現的畫面：&lt;/p&gt;
&lt;p&gt;「我該死的照了一堆照片，然後我該死的想整理它，但看了那個該死的數量後，我該死的放棄了」&lt;/p&gt;
&lt;p&gt;所以就來到今天的主題－－&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;python&quot;&gt;
&lt;h2&gt;如何用 Python 整理照片？&lt;/h2&gt;
&lt;p&gt;不同的人有不同的整理方式，但在整理前，大家的照片都一樣是混亂的(廢話，不然何必整理？)&lt;/p&gt;
&lt;p&gt;但話說回來，既然有不同的整理方式，那麼大家的「亂」法也應該不同吧？我簡單舉幾個可能性：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;不同的工具，好比說 iPhone 或 HTC New ONE，照出來的照片會有不同的命名方式，所以檔名混亂。&lt;/li&gt;
&lt;li&gt;先前傻傻的不知道，用 1, 2, 3, ... 10 而不是用 01, 02, 03, ... 10 的方式命名，所以瀏覽軟體顯示的圖片順序是錯誤的(照片或許沒差，但漫畫圖片就是地獄了)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;該怎麼辦呢？&lt;/p&gt;
&lt;p&gt;隨便打開一個文字編輯器，新增一個空白檔案，開始寫程式吧！&lt;/p&gt;
&lt;p&gt;先從簡單的問題開始，假設現在有一個資料夾，裡面有一堆照片，但因為這些照片的來源不只一個，有些是自己手機照的、有些媽媽手機照的、又有些是由 facebook 抓來的。不用的工具擁有不同的命名方式，雖然通常都是以時間來命名，但格式不同，所以現在檔名混亂。現在想要幫這些檔案重新以一致的格式命名，我該怎麼做呢？&lt;/p&gt;
&lt;p&gt;簡單看了一下需求，大概可以條列幾個步驟：&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;列出有那些檔案&lt;/li&gt;
&lt;li&gt;分析這些檔案&lt;/li&gt;
&lt;li&gt;然後用統一的規則為這些檔案改名&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Python 有一個 os 模組，這個模組提供了作業系統相關的功能，因為檔案的操作也包含在其中，所以我們可以由使用這個模組開始&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#listdir 會列出目標資料夾的所有檔案名稱&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;filenames&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;目標資料夾路徑&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;短短一行，我們便取得了所有檔案名稱。這裡面還有一些議題可以討論，假設一開始不知道確切路徑為何，我們可能會用到 os.path 模組相關的功能。像是用 os.path.expanduser 可以將「~」代換成家目錄實際的路徑，比如說：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expanduser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;~/Dropbox/pictures&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;home&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marco&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Dropbox&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pictures&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不過程式是為了方便使用，我覺得最簡單的使用方式就是直接將程式移到要目標資料夾中，直接點兩下執行，該資料夾的照片名稱就會被重新整理。我覺得這樣的流程是最簡單易懂的，所以直接檢查當前目錄就行了。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#取得當前目錄的所有檔案名稱(注意：結果會包含程式本身)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;filenames&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不過這裡面有很多檔案不是我要的，我只想要圖片而已，所以接下來我必須先篩選出那些才是我要的檔案。由於這篇文章的目的是希望藉一個例子來學習 Python 的各種常見的用法。反正時間不急，我們可以慢慢討論各種可能的用法，如果讀者可以想到更多好用的方法，歡迎在留言指出。&lt;/p&gt;
&lt;p&gt;要知道一個檔案是否為圖片檔，最簡單的方法就是直接看檔名末尾的副檔案，假設圖片檔就只有 .jpg 和 .png 兩種格式，那麼我們就可以假定檔名會是以 .jpg 或 .png 為結尾的字串，那麼我們可以這麼做：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#判斷檔案是否為圖片&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_imag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:]&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;.png&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;.jpg&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#取得所有圖片的檔名&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;images&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_imag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filenames&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;filter 是 Python 中非常實用的函式，功能是篩選序列中所有 item，它可以接受兩個參數，前者是條件函式，後者是目標序列。目標序列中所有 item 都會丟進條件函式來判斷是否保留，如果回傳 True，這個 item 就會在回傳的新序列中，反之則否。&lt;/p&gt;
&lt;p&gt;在這個例子中， is_imag 函式是我們用來判斷 filename 是否要保留的條件，如果為 True，那麼這個 filename 就會在 images 這個新序列之中。而我們判斷一個檔案的是否為圖片的依據是「檢查字串後四碼是否為 .jpg 或 .png ？如果是的話，我就當你是圖片！」。&lt;/p&gt;
&lt;p&gt;但話又說回來，沒人規定圖片的副檔名只能是三個字，比如說 .jpeg 或 .tiff 即是四個字，所以這種寫法並不是太好。如果碰到副檔名長度不同時，就必須一個個比較了。但這麼一來比較時，還得一一列出副檔案的長度，像是 filename[-5:] 或 filename[-4:]，顯然不是好的做法。&lt;/p&gt;
&lt;p&gt;或許我們可以改用字串的 endswith 函式來試試看。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#判斷檔案是否為圖片&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_imag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endswith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endswith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;.jpg&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#取得所有圖片的檔名&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;images&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_imag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filenames&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;字串的 endswith 函式可以檢查字串的尾端，如果字串是以輸入的參數結尾，就會回傳 True，反之則為 False。&lt;/p&gt;
&lt;p&gt;但這種做法如果副檔名的種類一多，程式碼就會顯得很冗长，這時我們可以用迴圈重構之。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#判斷檔案是否為圖片&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_imag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ext&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;.jpg&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endswith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;False&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#取得所有圖片的檔名&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;images&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_imag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filenames&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看到這個迴圈，熟悉 map 和 any 的人可能就會有感覺，因為這可以改成更精簡的版本：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#判斷檔案是否為圖片&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_imag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endswith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;.jpg&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#取得所有圖片的檔名&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;images&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_imag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filenames&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;map 可以接兩個參數，前者是函式，後者是目標序列。map 會將序列的每一個 item 丟給函式，然後將所有的函式回傳值當成新的序列，舉例如下：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;3.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;4.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;any 會判斷序列中的 item 有沒有存在 True，若有，就會回傳 True，若否則回傳 False。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;類似的函式有 all 函式，與 any 的區別是「序列中的 item 全部都是 True，才會回傳 True」&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;也許你會好奇，自己分析字串來找副檔名這麼麻煩，難道函式庫中沒有就可以專門抓出副檔名的函式嗎？&lt;/p&gt;
&lt;p&gt;其實還真的有，在 os.path 中有一個叫 splitext 的函式可以將檔案的副檔名抓出來，舉個例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;splitext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;a.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;.png&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;因此我們也可以改為：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#判斷檔案是否為圖片&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_imag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;splitext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;.jpg&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#取得所有圖片的檔名&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;images&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_imag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filenames&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;知道了所有圖片的檔名後，下一步就是分析這些檔案。假設我們的目的是統一用「1992-02-24.1.jpg」這種以「年-月-日.流水號.檔案格式」的格式重新為這些檔案命名，那麼我們在分析檔案時，就要先取得時間的資訊。&lt;/p&gt;
&lt;p&gt;假設有一個檔案名稱為「2014-10-04 12.49.44.jpg」，我們可以便可以輕易地從檔名取得時間資訊，要怎麼分析呢？最直覺的做法自然是使用正規表達式：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#取得圖片建立時間&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;#針對 Dropbox 的 Camera Uploads&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;\d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{4}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;-\d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{2}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;-\d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{2}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt; \d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{2}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;\.\d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{2}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;\.\d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{2}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;timestring&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strptime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;%Y-%m-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%d&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt; %H.%M.%S&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;#針對其他的命名格式&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這種方式的概念是將符合目標格式的檔案抓出來，然後丟給 datetime 的 datetime.strptime 函式處理，這個函式能依據給予的格式將目標字串轉換成時間的型態。格式的指定的方式可以參考 &lt;a class=&quot;reference external&quot; href=&quot;https://docs.python.org/2/library/datetime.html#strftime-strptime-behavior&quot;&gt;官方的文件&lt;/a&gt; 的內容。與之對應的還有 strftime，這個函式可以反過來將時間轉為指定格式的字串。&lt;/p&gt;
&lt;p&gt;我們其實也可以直接全由正規表達式來判斷時間，不經由 datetime.strptime 函式處理。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#取得圖片建立時間&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;#針對 Dropbox 的 Camera Uploads&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;(\d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{4}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;)-(\d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{2}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;)-(\d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{2}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;) (\d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{2}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;)\.(\d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{2}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;)\.(\d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{2}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;)&amp;#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hour&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;second&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;match_object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hour&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;second&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;#針對其他的命名格式&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不過，有些檔案的名稱並沒有提供時間的資訊，比如說「IMG_0995_JPG」就看不出時間為何？&lt;/p&gt;
&lt;p&gt;(也許能？但我不清楚規則，所以一樣沒辦法。)&lt;/p&gt;
&lt;p&gt;山不轉路轉，其實我們可以直接利用檔案建立的時間當作標準。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#取得圖片建立時間&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getmtime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fromtimestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#或是&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;st_mtime&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fromtimestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;知道所有圖片建立的時間後，最後就是將這些圖片的名稱改為指定的格式，可能的做法有兩個，分別為 shutil.move 和 os.rename，這裡我採用 shutil.move 來進行改名：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#將檔案依時間排序&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;filenames&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;last_modified&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filenames&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;modified&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;#決定流水號，若修改的日期與前一個檔案相同時流水號加 1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last_modified&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last_modified&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modified&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;#依據時間和流水號決定檔案&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;targetname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.jpg&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modified&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strftime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;%Y-%m-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%d&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;#改名&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shutil&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targetname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;last_modified&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modified&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;排序的相關操作可以參考我之前寫的 &lt;a class=&quot;reference external&quot; href=&quot;/articles/淺談-python-的排序/&quot;&gt;淺談 Python 的排序&lt;/a&gt;。至於之後的操作即是一些單純的程式邏輯，讓檔案依時間排序，比較前一個檔案的時間是否相同，若相同則流水號加 1 等，一個簡單的小程式就這麼寫完了。&lt;/p&gt;
&lt;p&gt;別看這篇文章似乎頗長，看起來很複雜，那是因為我們有討論多種可能性而已，事實上這個程式不到五十行就寫完了，沒有什麼思考上的難題，也不需要太過高深的程式技巧，差別只是你原先可能不知道 Python 原來還有這麼多方便的工具可以使用而已。&lt;/p&gt;
&lt;p&gt;以上。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[小說語句搜尋引擎]]></title>
        <id>https://blog.marco79423.net/articles/%E5%B0%8F%E8%AA%AA%E8%AA%9E%E5%8F%A5%E6%90%9C%E5%B0%8B%E5%BC%95%E6%93%8E/</id>
        <link href="https://blog.marco79423.net/articles/%E5%B0%8F%E8%AA%AA%E8%AA%9E%E5%8F%A5%E6%90%9C%E5%B0%8B%E5%BC%95%E6%93%8E/"/>
        <updated>2014-09-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[





名稱
小說語句搜尋引擎

使用語言
Python 2.7(需另裝 lxml)

使用平台
Unix-like(e.g. Linux, FreeBSD)

版本資訊 ...]]></summary>
        <content type="html"><![CDATA[&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;18%&quot; /&gt;
&lt;col width=&quot;82%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;名稱&lt;/td&gt;
&lt;td&gt;小說語句搜尋引擎&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用語言&lt;/td&gt;
&lt;td&gt;Python 2.7(需另裝 lxml)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用平台&lt;/td&gt;
&lt;td&gt;Unix-like(e.g. Linux, FreeBSD)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;版本資訊&lt;/td&gt;
&lt;td&gt;0.2.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;作者&lt;/td&gt;
&lt;td&gt;兩大類&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;下載連結&lt;/td&gt;
&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://blog.marco79423.net/backend/static/27897c4c-2cd0-11f1-bbd2-32abd871d697/&quot;&gt;載點&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;0.2.2 版更新內容&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;重新設計架構和程式碼&lt;/li&gt;
&lt;li&gt;新增支援多字詞搜尋&lt;/li&gt;
&lt;li&gt;自動使用 PAGER 瀏覽&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;對於寫作新手來說，如何使用字詞是一個重要的課題。比如想描述「煩惱」，該怎麼做呢？是苦痛的煩惱？奇異的煩惱？亦或是莫名的煩惱？這些都困擾著寫作新手，形成新手們的煩惱。&lt;/p&gt;
&lt;p&gt;參考別人的作品往往是一個好的出發點，多了解高手是怎麼使用文字，可以協助自己寫出更漂亮的文章。但參考作品的品質也必須有所限定，畢竟參考垃圾對文筆沒什麼幫助。&lt;/p&gt;
&lt;p&gt;但要如何找出值得參考的文章呢？&lt;/p&gt;
&lt;p&gt;這顯然無法透過 Google 搜尋字詞解決，我們雖然可以藉此找出含有相同詞彙的文章，但卻沒什麼意義。因為 Google 只在意文章是否相關，它並十分不在乎文筆的好壞，因此找出來的文章參考價值有限。&lt;/p&gt;
&lt;p&gt;為了解決這個問題，此專案的目標即是做一個文字搜尋引擎，只要輸入關鍵字詞，便會後回傳完整的語句。&lt;/p&gt;
&lt;p&gt;我的構想是「一個廣受大家喜愛的作品，其文筆至少不會太差」，所以我找了一個名為「小說頻道」的網站，抓取小說公開的內容當作資料來源，並以「小說排行榜」的排序做為篩選的依據，只要排行比較前面的小說，就更有可能被當成結果顯示。透過這樣的篩選，理論上我就可以找出不錯的句子。&lt;/p&gt;
&lt;p&gt;使用方法很簡單，只要做兩件事：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;更新語句資料庫&lt;/li&gt;
&lt;li&gt;搜尋&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;更新即是去「小說頻道」抓取資料，所以要保證網路的暢通，抓取的過程可以隨時用 Ctrl + C 中斷，畢竟「小說頻道」一共有上千部小說，如果要等到全部抓完，將會花上許多時間和硬碟空間，因此只需要抓取足夠量的小說即可。&lt;/p&gt;
&lt;p&gt;由於抓取的順序是以排行榜第一名的小說開始，相信搜尋出的語句都能有不錯的表現。如果發現搜尋不到句子，或是結果太少，可再重新下「更新」指令，抓取更多小說。&lt;/p&gt;
&lt;p&gt;更新的語法如下：&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2788f60a-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2788f60a-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;等到抓完足夠多的小說後，便可以用 Ctrl + C 中斷，接下來就可以搜尋了。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;資料庫會存放在家目錄之中，檔名為 .nch.db&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;比如說想搜尋「我們」，可以如下操作：&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/27892c38-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/27892c38-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;也可以同時搜尋多個字詞，如「我們」、「他們」：&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/27886afa-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/27886afa-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;除此之外還有一個功能，如果 grep 後面不接任何參數，就會進入互動模式，可以搜尋多次關鍵字。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2788bfe6-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2788bfe6-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;如果不想要搜尋時，只要輸入 Ctrl + C 即可跳出程式。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2788151e-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2788151e-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[淺談 Python 的屬性]]></title>
        <id>https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-python-%E7%9A%84%E5%B1%AC%E6%80%A7/</id>
        <link href="https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-python-%E7%9A%84%E5%B1%AC%E6%80%A7/"/>
        <updated>2014-06-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[
Note
這篇文章只討論新式類別(new-style class)的情況，理由是如果再加上舊式類別(old-style class) 的用法，問題會變得太過複雜，不利於理解……雖然現在這樣還是頗難懂就是了。

什麼是屬性(Attribute)？
Python ...]]></summary>
        <content type="html"><![CDATA[&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;這篇文章只討論新式類別(new-style class)的情況，理由是如果再加上舊式類別(old-style class) 的用法，問題會變得太過複雜，不利於理解……雖然現在這樣還是頗難懂就是了。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;什麼是屬性(Attribute)？&lt;/p&gt;
&lt;p&gt;Python 的屬性概念包含非常廣泛，基本上所有「.」之後的都是屬性。由於 Python 所有東西都是物件，所以取得某物件的屬性也可說是「從一個物件中取得附屬於該物件的另一個物件」。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;#weight 是 chicken 的屬性&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Python 的屬性可以分為「實例屬性(instance attribute)」和「類別屬性(class attribute)」兩種。實例屬性又稱資料屬性(data attribute)，類似於 C++ 的成員變數(member variable)；至於類別屬性則類似於 C++ 的靜態成員變數(static member variable)。&lt;/p&gt;
&lt;p&gt;所有的資料屬性都是伴隨著實例生成，因此每一個實例的資料屬性都是互相獨立的，當修改其中一實例的資料屬性時，並不會影響到其他實例。另一方面，類別屬性並不會隨著實例而生成，甚至不需要產生任何實例便可直接存取，每一個實例所存取的類別屬性都將是同一個，也就是說當我們修改類別屬性時，也會影響到別的實例存取。換個角度來看，由於 Python 所有東西都是物件，因此類別自然也可看作是一個物件，而類別屬性即是此物件的資料屬性，而因為類別只有一個，所以實例存取時都是存取同一個屬性。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#類別屬性&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;age&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#實例屬性(or 資料屬性)&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;age&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;age&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;#可以藉 c 直接存取類別屬性(與存取 Chicken.weight 相同)&lt;/span&gt;
&lt;span class=&quot;mf&quot;&gt;1.1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;物件裡預設會有 __dict__ 這個特殊屬性，不需我們手動建立。 __dict__ 是一個字典物件(dict)，裡頭會自動存放著物件的所有自定義的屬性。事實上，這也是屬性實際存放的位置。我們可以直接透過 __dict__ 存取物件的屬性，在一些情形下，這是一個有用的技巧：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;vm&quot;&gt;__dict__&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#__dict__ 是個字典物件&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;age&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;vm&quot;&gt;__dict__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;age&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#與存取 c.age 同義&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;abc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#可以隨時定義新的屬性&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;vm&quot;&gt;__dict__&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;age&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;abc&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你也許會好奇，此例既然可以用 c.weight 存取類別屬性，為何 __dict__ 並沒有 weight 屬性呢？那是因為無論 c.weight 還是 Chicken.weight 所指的屬性位置都是在 Chicken 的 __dict__ 中。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;vm&quot;&gt;__dict__&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dictproxy&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;__doc__&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s1&quot;&gt;&amp;#39;__init__&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s1&quot;&gt;&amp;#39;__module__&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s1&quot;&gt;&amp;#39;get_age&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s1&quot;&gt;&amp;#39;weight&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;#&amp;lt;= 在這裡！&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;換句話說，Python 在搜尋物件的屬性時，並不只針對物件本身，還會搜尋其類別的屬性，實際搜尋的順序如下：&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;物件本身的屬性&lt;/li&gt;
&lt;li&gt;其類別的屬性&lt;/li&gt;
&lt;li&gt;類別的父類別或祖先類別的屬性&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(有例外，後面會說明)&lt;/p&gt;
&lt;p&gt;我們可以簡單的證明這一點，假若 Chicken 類別再定義一個與類別屬性 weight 同名的實例屬性：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#類別屬性&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#實例屬性&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;
&lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;             &lt;span class=&quot;c1&quot;&gt;#證明實例屬性會優先於類別屬性&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;回頭看 Chicken 類別可以發現 get_age 這個函式也包含在 __dict__ 中了，這似乎代表 get_age 函式其實也是一個類別屬性。&lt;/p&gt;
&lt;p&gt;但這個 get_age 似乎有點不一樣？再進一步觀察，可以發現由類別呼叫這個函式與由物件呼叫這個函式的結果似乎也不一樣。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;vm&quot;&gt;__dict__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;get_age&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bound&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;at&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x02A192B0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unbound&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;仔細想想，這並不奇怪，因為函式由類別呼叫或是由物件本身呼叫的行為本來就不同。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#如果不知道是那一個物件，就不知道 age 為何，因此必須加上指向物件的參數&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這也很好的解釋了剛剛顯示的資訊，Chicken 的 get_age 是 unbound 的，因為沒有綁定在某一個物件上，所以必須加上指向物件的參數，而 c 則不需要。&lt;/p&gt;
&lt;p&gt;不過這樣一來，卻產生了一個新的困惑，我們已經知道屬性實際是存放在 __dict__  中，但這個名為 get_age 的屬性卻同時有兩種不同行為！&lt;/p&gt;
&lt;p&gt;為什麼呢？&lt;/p&gt;
&lt;p&gt;事實上，這個名為 get_age 的神秘屬性有一個正式的名稱叫做「描述器(Descriptor)」，其功能就是產生給類別用的 unbound 函式和產生給物件用的 bound 函式。&lt;/p&gt;
&lt;p&gt;描述器即是支援下列三個函式的物件：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
__get__(self, obj, type=None) --&amp;gt; value

__set__(self, obj, value) --&amp;gt; None

__delete__(self, obj) --&amp;gt; None
&lt;/pre&gt;
&lt;p&gt;但並非三者都必須同時支援，描述器可再細分為「資料描述器(Data Descriptor)」和「非資料描述器(Non-Data Descriptor)」兩種，其中「非資料描述器」只需要支援 __get__ 即可，而前述的 get_age 便是一個「非資料描述器」。&lt;/p&gt;
&lt;p&gt;Python 在存取物件的屬性時，若發現取出的物件是一個描述器時，接下來的動作無論是讀取或是修改都會交由描述器自己處理。比如說當發現 c.get_age 是一個描述器時，回傳的並不是描述器本身(也就是 get_age)，而是執行 get_age.__get__()，然後回傳該函式執行的結果。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bound&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;at&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x02A192B0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;vm&quot;&gt;__dict__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;get_age&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__get__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#與前者同義&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bound&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;at&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x02A192B0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;vm&quot;&gt;__dict__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;get_age&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__get__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)()&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;#與前者同義&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;__get__ 可以接受兩個參數，第一個參數代表對應的物件實例，第二個參數則是該物件對應的類別。為何要如此設計呢？因為每一個實例都可以擁有個別的屬性值，因此描述器回傳的屬性值必須要能根據不同的實例而有所不同。除此之外，除了實例可以呼叫屬性外，其類別也要能呼叫同樣的屬性(e.g. Chicken.get_age)，所以我們必須要能區分是實例呼叫的還是類別自己呼叫的。&lt;/p&gt;
&lt;p&gt;基於以上理由，第一個參數存在的目的就是讓描述器能夠根據不同的物件實例回傳對應的屬性值。若由類別呼叫，因為沒有實例(第一個參數傳入 None)，因此必須要由第二個參數告訴描述器究竟是由那個類別呼叫。由此可知，因為這樣的設計，所有不同實例和類別的該屬性值都可以直接存放在唯一的那個描述器中，不需要存放在個別的物件實例中。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;vm&quot;&gt;__dict__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;get_age&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__get__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#c.get_age 實際使用的函式&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_age&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__get__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#Chicken.get_age 實際使用的函式&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;既然提到了非資料描述器，免不了也要提一下資料描述器，資料描述器與非資料描述器差別在於多了 __set__ 或 __delete__。若定義了 __set__ 便可用 instance.attr=3 這種寫法修改屬性值；而定義了 __delete__ 則可以用 del instance.attr 刪除屬性。&lt;/p&gt;
&lt;p&gt;為什麼我要強調非資料描述器和資料描述器的不同呢？不是只差別在可否修改而已嗎？那是因為兩者在 Python 中有完全不同的優先權。Python 在尋找某物件的屬性時，好比說要尋找 c.attr ，是根據以下順序尋找的：&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;尋找其類別的 __dict__ 有沒有 attr，若有而且 attr 是資料描述器就直接回傳 __get__ 的結果&lt;/li&gt;
&lt;li&gt;尋找 c 自己的 __dict__ 有沒有 attr，若有便直接回傳&lt;/li&gt;
&lt;li&gt;若剛剛類別的 __dict__ 其實有 attr，但 attr 卻是非資料描述器時，現在便回傳其 __get__ 結果&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;簡單來說，就是先看有沒有資料描述器，然後再找實例自己，若自己沒有就再找非資料描述器。你也許會很驚訝，有沒有定義 __set__ 或 __delete__ 竟有這麼大的差別？&lt;/p&gt;
&lt;p&gt;話又說回來，Python 大費周章的定義了「描述器」這個概念，描述器又能用來做什麼呢？在操作描述器這種屬性時，我們可以用函式來定義 c.attr、c.attr=3 和 del c.attr 的行為。&lt;/p&gt;
&lt;p&gt;這提供了我們很大的彈性來操作屬性，我們可以在看似單純的給某物件的變數賦值時做處理，比如說檢查賦值時型態的正確性或是在賦值前修改值的內容；在存取時，也可以是動態的計算屬性值的最新狀態；若需要很多資源，還能用快取的方式記錄屬性增加存取的速度。而且因為描述器的特性，描述器能夠查覺究竟是被物件呼叫還是類別呼叫，我們也可以強迫某屬性只能由物件或類別來呼叫。&lt;/p&gt;
&lt;p&gt;不過若是每一個需要使用描述器的屬性都要各自實作一個類別顯然不夠方便，反正關鍵在於我們希望能自行定義屬性在存取、賦值和刪除時的行為，所以 Python 另外提供了一個方便的做法，那就是使用 property。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fget&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fdel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;舉個例子，假設我想在 Chicken 這個類別定義 weight 這個屬性，存取、賦值和刪除的行為都要能自己定義，我可以這麼做：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;非常重！&amp;quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;不給改~~&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;del_weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;而且不給刪~&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set_weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;del_weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;這是一個邪惡的屬性&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;另一種更簡單的寫法：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@property&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;非常重&amp;quot;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setter&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;不給改~~&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deleter&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;而且不給刪~&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這整個流程包括前述的讀取順序都是在 __getattribute__ 這個函式實現的，我們也可以覆寫這個函式，取代原本的功能。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@property&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;非常重&amp;quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__getattribute__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;sd&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;sd&quot;&gt;        覆寫 __getattribute__，改變原本的行為&lt;/span&gt;
&lt;span class=&quot;sd&quot;&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;weight&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;其實沒很重&amp;quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__getattribute__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如此一來，當存取 c.weight 屬性時，回傳的就不會是「非常重」而是「其實沒很重」了。另外，由於這個函式是最先被呼叫的，因此這個函式可說是存取屬性時的第一道門，我們可以用這個函式直接改變 Python 原本存取屬性時的行為，比如說修改存取屬性的順序或使用快取。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__getattribute__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrname&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#使用快取&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__getattribute__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但因為每一個屬性都會呼叫 __getattribute__，甚至包括 __XXX__ 這種特別的屬性，所以使用時一定要非常小心。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;要注意 __getattribute__ 是 new-style class 才能使用的功能。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;另外，對應讀取屬性的行為，自然也會有修改和刪除，這兩個函式分別為 __setattr__()、__delattr__()。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
__setattr__(self, attrname, value)  --&amp;gt; None

__delattr__(self, attrname)  --&amp;gt; None
&lt;/pre&gt;
&lt;p&gt;這兩者使用的感覺和前者相近，就不復述了，但記得使用 __getattribute__ 和 __setattr__ 這類的函式時一定要小心無限迴圈的問題，比如說千萬不能這麼做：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__getattribute__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;weight&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;#千萬別這麼做！&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__getattribute__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;理由是 self.weight 會再呼叫一次 __getattribute__，然後裡頭的 self.weight 又會再呼叫一次 __getattribute__，如此不斷地進行下去，直到程式死給你看。&lt;/p&gt;
&lt;p&gt;事實上，這幾個函式在沒有必要時，個人不推薦使用，因為通常我們不會隨便「同時修改所有存取屬性的行為」，若是個別的修改，也可以使用描述器。如果是要用來處理不存在的屬性時，也有 __getattr__ 可以使用。&lt;/p&gt;
&lt;p&gt;__getattr__ 看起來與 __getattribute__ 非常相似，很容易搞混，而且用法也幾乎一模一樣，差別在於 __getattr__ 只會在找不到屬性時才會被呼叫。所以說我們可以用這個函式來專門處理「不存在的屬性」，比如說：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

        &lt;span class=&quot;nd&quot;&gt;@property&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;非常重&amp;quot;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__getattr__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;哭哭，您呼叫的屬性不存在！&amp;quot;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chicken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fadfadfafa&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;#會印出「哭哭，您呼叫的屬性不存在！」&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最後總結一下 Python 整個搜尋屬性的過程：&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;有定義 __getattribute__()，便回傳執行結果。若擲出 AttributeError 異常，則仍會繼續下一步。&lt;/li&gt;
&lt;li&gt;搜尋是否有資料描述器，若有則回傳其 __get__() 的結果&lt;/li&gt;
&lt;li&gt;搜尋 __dict__ 中是否有相符的屬性名稱，若有則回傳&lt;/li&gt;
&lt;li&gt;搜尋是否有非資料描述器，若有則回傳其 __get__() 的結果&lt;/li&gt;
&lt;li&gt;若有定義 __getattr__()，則回傳執行結果。但如果擲出 AttributeError 異常，則會繼續下一步。&lt;/li&gt;
&lt;li&gt;若實在找不到，便擲出 AttributeError 異常&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這篇文章大概整理了一下 Python 屬性的使用，要記得這裡提到的用法都是單指新式類別的情況，因為舊式類別的運作有些許的不同，好比說 __getattribute__ 就只能在新式類別才能使用，所以使用時一定要注意這一點。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[架設個人部落格的心路歷程]]></title>
        <id>https://blog.marco79423.net/articles/%E6%9E%B6%E8%A8%AD%E5%80%8B%E4%BA%BA%E9%83%A8%E8%90%BD%E6%A0%BC%E7%9A%84%E5%BF%83%E8%B7%AF%E6%AD%B7%E7%A8%8B/</id>
        <link href="https://blog.marco79423.net/articles/%E6%9E%B6%E8%A8%AD%E5%80%8B%E4%BA%BA%E9%83%A8%E8%90%BD%E6%A0%BC%E7%9A%84%E5%BF%83%E8%B7%AF%E6%AD%B7%E7%A8%8B/"/>
        <updated>2014-01-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[之所以架設這個部落格，最初是因為實驗室老師要求。
雖然老師的目的應該只是希望我們每人都能有自己專屬的網頁，能放一些研究相關的資料而已。本來這種如作業般的無聊事隨便做做即可，但我突然想到以前室友有在經營一個部落格，好像還挺有意思。而且我也發現我似乎不曾認真架過完整的網站，於是便決定趁此機會架設一個屬於自己的部落格。
我開始思索部落格應該要寫些什麼內容？花了些許時間，總結後，決定了網站成立的目標－－整合所學，促進自我學習 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;之所以架設這個部落格，最初是因為實驗室老師要求。&lt;/p&gt;
&lt;p&gt;雖然老師的目的應該只是希望我們每人都能有自己專屬的網頁，能放一些研究相關的資料而已。本來這種如作業般的無聊事隨便做做即可，但我突然想到以前室友有在經營一個部落格，好像還挺有意思。而且我也發現我似乎不曾認真架過完整的網站，於是便決定趁此機會架設一個屬於自己的部落格。&lt;/p&gt;
&lt;p&gt;我開始思索部落格應該要寫些什麼內容？花了些許時間，總結後，決定了網站成立的目標－－整合所學，促進自我學習，和展示自我的空間。
所以我打算寫技術相關的文章和一些學習心得。&lt;/p&gt;
&lt;p&gt;不過在寫部落格文章之前，總要先有部落格才行。為此我準備好適當的環境，並查了許多相關資料。&lt;/p&gt;
&lt;p&gt;查詢的過程中，有一篇文章提供了我一些想法－－當美國總統歐巴馬在參加總統大選時，其募款網站之所以可以應付超大流量、並保證安全性，有一個很重要的理由便是採用靜態網頁來取代動態網頁。&lt;/p&gt;
&lt;p&gt;這讓我有一些感觸，曾幾何時，每個初學網站製作的工程師，都會以製作動態網頁為目標，先學會 PHP、ASP.net 之類的語言後，才開始製作網站。不管合不合適，都一定要使用到資料庫系統，彷彿沒用到資料庫就會落後人一截似的。說實話，個人網頁的資料真的有多到必須使用資料庫嗎？我很懷疑。&lt;/p&gt;
&lt;p&gt;因此，我決定用靜態網頁的方式架設我的部落格，理由是部落格需要和別人互動的地方很少，留言的部分只要交給 DISQUS 就行了。而且我也不是什麼高手，萬一網站被人打壞了，我會很傷心的。至少用靜態網頁的方式，我的網站也會安全許多。&lt;/p&gt;
&lt;p&gt;參考了很多自行架設部落格的前輩後，我發現「 &lt;a class=&quot;reference external&quot; href=&quot;http://octopress.org/&quot;&gt;Octopress&lt;/a&gt; 」這個名字不斷地在我眼前出現，不斷地出現，不斷該死地出現……似乎認為只要出現多次一點，就能讓我忘記 &lt;a class=&quot;reference external&quot; href=&quot;http://octopress.org/&quot;&gt;Octopress&lt;/a&gt; 是用 Ruby 這個「敵方陣營」的語言開發似的。&lt;/p&gt;
&lt;p&gt;士可殺不可辱！就算是泥人也是有火氣的……雖然我確實是有點想學 Ruby 沒錯啦，但這篇文章的分類畢竟是「Python」不是？&lt;/p&gt;
&lt;p&gt;所以我決定徹底的唱反調！假使在開發過程中有任何地方會用到該死的 Ruby，就一定要找出 Python 的替代品取代……至少「精神上」一定要與 Ruby 無關才行！&lt;/p&gt;
&lt;p&gt;我花了許多時間找尋類似 &lt;a class=&quot;reference external&quot; href=&quot;http://octopress.org/&quot;&gt;Octopress&lt;/a&gt; 的工具，也許是因為我加了一些條件，所以選擇其實不是很多。好比說我想用 reStructuredText 來寫部落格文章，但偏偏大部分的工具都只支援 Markdown。另外，我也不喜歡部落格文章是以時間來分類，畢竟我的目標不是寫日記，同一篇文章我可能會反覆修改多次，這對這類似的工具來說，其實是很奇怪的要求，因此選擇又更少了。&lt;/p&gt;
&lt;p&gt;最後，我找到了 &lt;a class=&quot;reference external&quot; href=&quot;http://getpelican.com&quot;&gt;Pelican&lt;/a&gt; ，這個工具非常好用，基本可以符合我的要求。使用方式也很簡單，我可以用 reStructuredText 寫我的文章，然後將文章放置在指定的資料夾內，就可以透過該工具直接轉換成一個網站。接下來，只要調整一下 Apache 的設定，我的部落格就誕生了！&lt;/p&gt;
&lt;p&gt;不過事情還沒結束，雖然部落格有了，但我希望我的部落格能有更多個人風格，因此我必須重新設計網站的前端才行。我研究了 Pelican 的程式碼，發現它似乎是利用 &lt;a class=&quot;reference external&quot; href=&quot;http://jinja.pocoo.org/&quot;&gt;Jinja2&lt;/a&gt; 樣版引擎轉換網頁內容：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;head&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;{{chicken}}&lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;head&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;只要事先寫好類似上面的樣版， &lt;a class=&quot;reference external&quot; href=&quot;http://jinja.pocoo.org/&quot;&gt;Jinja2&lt;/a&gt; 就可以將「{{chicken}}」這種類似變數的東西代換成實際的網頁內容。也就是說，只要了解這種樣版的使用方式，我便可以藉此設計出屬於自己的網站了。這對我來說不算困難，因為這和 Django 的樣版引擎非常相似。&lt;/p&gt;
&lt;p&gt;說到底，這其實還是 HTML，只是多了一些變數、迴圈的概念而已，所以我只需要好好的學習一下 HTML 和 CSS 即可。我查了一些資料，發現 HTML5/CSS3 最近很流行，所以我希望我的網站能盡量符合 HTML5 的標準。而我現今網站有一個小彩蛋－－如果滑鼠放在上面網站左上角的標誌，標誌就會開始旋轉。這個技巧，便是應用了 CSS3 的特性。&lt;/p&gt;
&lt;p&gt;外觀設計花了我很長很長的時間，事實上，我大部分的開發時間都花在調整配色上面，這是屬於我的地獄，因為我對色彩似乎不太敏感，所以成果一直不甚滿意。到後來，因為花的時間太長了，心靈受到的創傷太大了，所以從此以後，我之後的專案在想不到用什麼顏色時，就會直接套用這個網站的配色，節省麻煩和時間。&lt;/p&gt;
&lt;p&gt;我覺得對新手來說，前端最難搞的就是 CSS 了。相信光是要讓內容在畫面上置中可能就已經搞死一堆人了，更別提很多進階的用法是網路上很難查到的。網路上的資料解決的通常是一個片斷的問題，給予的答案也是片斷的答案。如果真的用這些零零碎碎的資訊來拼湊出一個網站，效果多半不怎麼樣。&lt;/p&gt;
&lt;p&gt;不過幸好網路上也有許多高手為了協助人們解決這個問題，製作了許許多多 CSS 框架，這些框架把很多常見的問題標準化，協助我們解決大部分在前端設計時所碰到的各種麻煩。這些框架有的非常強大而複雜，也有的輕巧而易學，像是 Bootstrap 便相當有名，這個框架非常輕巧，而且非常容易學習，我有很多熟悉網站製作的朋友都會使用，所以……本著叛逆的精神，我決定選用 &lt;a class=&quot;reference external&quot; href=&quot;http://foundation.zurb.com/&quot;&gt;Foundation&lt;/a&gt; 這個框架，其實兩者相當類似，功能也差不多，不過我私心認為 &lt;a class=&quot;reference external&quot; href=&quot;http://foundation.zurb.com/&quot;&gt;Foundation&lt;/a&gt; 的畫面好看不少，所以才做出這樣的選擇。&lt;/p&gt;
&lt;p&gt;很快地，我的設計限入了瓶頸，有些構想無法達成。原因是 &lt;a class=&quot;reference external&quot; href=&quot;http://getpelican.com&quot;&gt;Pelican&lt;/a&gt; 會自動將 reStructuredText 文章轉成 HTML 的形式，並與網站的內容結合。也就是說部落格文章內文的部分所用的 HTML 標籤是自動生成的，無法手動更改，偏偏有些標籤性質就是這樣，我無法藉由調整 CSS 來讓畫面變成我想要的樣子。&lt;/p&gt;
&lt;p&gt;為了解決這個問題，我研究了一下 &lt;a class=&quot;reference external&quot; href=&quot;http://getpelican.com&quot;&gt;Pelican&lt;/a&gt; 的程式碼，發現它是利用 &lt;a class=&quot;reference external&quot; href=&quot;http://docutils.sourceforge.net/&quot;&gt;Docutils&lt;/a&gt; 這個工具來來處理 reStructuredText 的文章，並將文章轉為 HTML 的格式。所以我修改了 &lt;a class=&quot;reference external&quot; href=&quot;http://docutils.sourceforge.net/&quot;&gt;Docutils&lt;/a&gt; 這個模組的部分內容，並順便新增一些我需要的功能。我修改了 Reference 表現的形式、新增了變數 raw_summary (為了讓 facebook, G+ 這種網站可以截取文章的片段)等。&lt;/p&gt;
&lt;p&gt;除此之外，務實的我為了好好與 Ruby 鬧彆扭，我研究了一下那些使用 Ruby on Rails 的傢伙平常會使用什麼工具。沒想到，還真的發現不少有趣的玩意兒，比如說 &lt;a class=&quot;reference external&quot; href=&quot;http://haml.info/&quot;&gt;Haml&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;不得不說，我一直懷疑 &lt;a class=&quot;reference external&quot; href=&quot;http://haml.info/&quot;&gt;Haml&lt;/a&gt; 的開發者是不是 Python 使用者叛變過去的，因為點子實在是太一致了。居然懂得利用「縮排」來減少 HTML 的標籤，這真是太聰明了，下面是 &lt;a class=&quot;reference external&quot; href=&quot;http://haml.info/&quot;&gt;Haml&lt;/a&gt; 的使用實例：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;%html&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;%head&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;%title&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;我是標題
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;%body&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;我是內文
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這種表示方式簡單到我一看例子就知道怎麼使用了，實在是太棒了！所以我立刻決定採用。使用方式也很簡單，只要先用這種 &lt;a class=&quot;reference external&quot; href=&quot;http://haml.info/&quot;&gt;Haml&lt;/a&gt; 的格式設計網頁，然後再透過工具轉為 HTML 格式就行了。整個過程唯一礙眼的是這個工具是用 Ruby 開發的……因此我決定找尋 Python 版本的替代品。&lt;/p&gt;
&lt;p&gt;Python 版的替代品不難找，甚至稱得上是非常容易，問題反而是替代品太多了，讓人不知道怎麼選擇，而且每一個版本或多或少都和原版有些許的出入。有的修改非常不錯，而且很有 Python 的風格，但因為原版太過有名，很多編輯器都有支援，所以這些修改，反而可能會讓這些編輯器出現錯誤，造成了許多麻煩。&lt;/p&gt;
&lt;p&gt;我找了很久，突然發現 &lt;a class=&quot;reference external&quot; href=&quot;http://jinja.pocoo.org/&quot;&gt;Jinja2&lt;/a&gt; 樣版引擎其實就有支援使用 &lt;a class=&quot;reference external&quot; href=&quot;http://haml.info/&quot;&gt;Haml&lt;/a&gt; 版本的外掛 &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/Pitmairen/hamlish-jinja&quot;&gt;Hamlish-jinja&lt;/a&gt;，既然如此，那還有什麼好猶豫的呢？用下去就對了！&lt;/p&gt;
&lt;p&gt;雖然這樣看起來 &lt;a class=&quot;reference external&quot; href=&quot;http://jinja.pocoo.org/&quot;&gt;Jinja2&lt;/a&gt; 好像很累，每一個網頁都必須先經由 &lt;a class=&quot;reference external&quot; href=&quot;http://haml.info/&quot;&gt;Haml&lt;/a&gt; 轉換為 &lt;a class=&quot;reference external&quot; href=&quot;http://jinja.pocoo.org/&quot;&gt;Jinja2&lt;/a&gt; 樣版，然後才轉成 HTML。不過這不是什麼大問題，因為我只要轉一次即可，對觀看網站的使用者來說，他們看的版本都是事先早已轉成 HTML 的網頁，而不是看的時候才動態產生的，所以並不會影響使用的體驗。&lt;/p&gt;
&lt;p&gt;我還發現另一個好用的玩意兒則是 &lt;a class=&quot;reference external&quot; href=&quot;http://sass-lang.com/&quot;&gt;SCSS&lt;/a&gt; 。如果說 &lt;a class=&quot;reference external&quot; href=&quot;http://haml.info/&quot;&gt;Haml&lt;/a&gt; 是針對 HTML 的話，那麼 &lt;a class=&quot;reference external&quot; href=&quot;http://sass-lang.com/&quot;&gt;SCSS&lt;/a&gt; 便是針對 CSS，&lt;a class=&quot;reference external&quot; href=&quot;http://sass-lang.com/&quot;&gt;SCSS&lt;/a&gt; 新增一些語法來減少 CSS 的程式碼，藉此增進開發效率，減少錯誤的機會。我認為 &lt;a class=&quot;reference external&quot; href=&quot;http://sass-lang.com/&quot;&gt;SCSS&lt;/a&gt; 最大的奧妙在於可以巢狀定義 CSS，這樣可以大量減少 class 的使用。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* Main */&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;#main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;sub-dominant-color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*/&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;#content&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;article&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;很好用對吧？非常好，找 Python 的版本！&lt;/p&gt;
&lt;p&gt;由於 &lt;a class=&quot;reference external&quot; href=&quot;http://jinja.pocoo.org/&quot;&gt;Jinja2&lt;/a&gt; 可以支援使用 &lt;a class=&quot;reference external&quot; href=&quot;http://webassets.readthedocs.org/&quot;&gt;webassets&lt;/a&gt; ，所以我可以輕易的透過 &lt;a class=&quot;reference external&quot; href=&quot;http://webassets.readthedocs.org/&quot;&gt;webassets&lt;/a&gt; 的幫助，找到可以使用的 Python 版本的 &lt;a class=&quot;reference external&quot; href=&quot;http://sass-lang.com/&quot;&gt;SCSS&lt;/a&gt; 。你可以把 &lt;a class=&quot;reference external&quot; href=&quot;http://webassets.readthedocs.org/&quot;&gt;webassets&lt;/a&gt; 當成一個篩選器，只有符合要求的檔案經過它，就會自動轉為指定的格式：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- 此例即是將 style.scss 轉為「style-local.css --&amp;gt;&lt;/span&gt;
-assets filters=&amp;quot;pyscss&amp;quot;, output=&amp;quot;style-local.css&amp;quot;, &amp;quot;style.scss&amp;quot;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;剛好有一個 &lt;a class=&quot;reference external&quot; href=&quot;http://webassets.readthedocs.org/&quot;&gt;webassets&lt;/a&gt; 的外掛可以支援 &lt;a class=&quot;reference external&quot; href=&quot;http://sass-lang.com/&quot;&gt;SCSS&lt;/a&gt; 的轉換，也就是 &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/Kronuz/pyScss/&quot;&gt;pyScss&lt;/a&gt; ，這麼一來，我只要安裝 &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/Kronuz/pyScss/&quot;&gt;pyScss&lt;/a&gt; 就可以直接使用。藉著這個機會，我也順便使用了一些 &lt;a class=&quot;reference external&quot; href=&quot;http://webassets.readthedocs.org/&quot;&gt;webassets&lt;/a&gt; 支援的優化工具，比如說 &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/zacharyvoase/cssmin&quot;&gt;cssmin&lt;/a&gt; 。顧名思義，這個工具可以壓縮 CSS 以減少傳輸量。聽起來很厲害，但其實壓縮的原理不難，最簡單的做法就是將所有的換行符號去掉，反正換行是方便人看的，電腦讀取的時候其實不需要。&lt;/p&gt;
&lt;p&gt;網站的外觀完成後，還有一些細節需要處理，比如說要加上 facebook 或 Google+ 的按讚功能來增加網站被人看到的機會，還要加上一些 facebook 專用的標籤，才能方便別人在 facebook 分享網站時能正確抓到我想要顯示的簡介。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/249a8724-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/249a8724-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;最後，這麼一個沒多少人看的部落格就這麼誕生了！&lt;/p&gt;
&lt;p&gt;整體而言，我對成果還算滿意，也得到許多成就感。我覺得做網站最大的樂趣在於只要懂得使用一些工具，你便可以在短短一天的時間內做出外行人看起來好像很厲害的東西，然後享受人家的讚美，這也難怪這麼多人喜歡開發網站，這不是沒有原因的。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[淺談 Python 的排序]]></title>
        <id>https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-python-%E7%9A%84%E6%8E%92%E5%BA%8F/</id>
        <link href="https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-python-%E7%9A%84%E6%8E%92%E5%BA%8F/"/>
        <updated>2013-12-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[
Note
本文以 Python 2 為例。

排序，即是將一組資料依據使用者的需求重新排列，以便我們查詢和分析資料。
舉例來說，我想獎勵班上前三名的同學和處罰後三名的同學，就可以將全班同學依成績由高到低排序。如此一來，序列的前三位同學即前三名，後三位則為後三名，我可以輕易的鎖定目標 ...]]></summary>
        <content type="html"><![CDATA[&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;本文以 Python 2 為例。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;排序，即是將一組資料依據使用者的需求重新排列，以便我們查詢和分析資料。&lt;/p&gt;
&lt;p&gt;舉例來說，我想獎勵班上前三名的同學和處罰後三名的同學，就可以將全班同學依成績由高到低排序。如此一來，序列的前三位同學即前三名，後三位則為後三名，我可以輕易的鎖定目標，將後三名的同學的頭輕輕扭下來，送給前三名當球踢……&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;python-1&quot;&gt;
&lt;h2&gt;Python 的排序&lt;/h2&gt;
&lt;p&gt;1960 年代，電腦製造商曾經做過一項調查，發現他們製造的電腦有 25% 的計算時間是在「排序」上，很多電腦甚至遠遠超過這個數字。「排序」是如此重要，Python 自然會提供一些方法來協助我們解決有關排序的問題。&lt;/p&gt;
&lt;p&gt;首先是獨立的 sorted 函式，使用方式非常直覺，所以我直接舉一個例子說明用法：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grades&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;70&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grades&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;70&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;sorted 函式可以接受一個序列當參數，而後回傳已排序好的新序列，預設是由小排到大。要注意的是 sorted 函式會產生一個新序列來排序，並不會影響舊序列的內容。&lt;/p&gt;
&lt;p&gt;如果是串列的話，還可以使用自帶的 sort 函式，一樣直接舉個例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grades&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;70&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grades&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grades&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;70&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;與前者不同，sort 函式並沒有回傳值，它會直接排序串列的內容。也因為不用另外花時間建構一個新的序列回傳，因此這個函式的效率會比 sorted 函式好上一些，我們可以使用模組 timeit 證明這一點：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; python –mtimeit –s’import random&#39; &#39;x=range(1000); random.shuffle(x)&#39;
1000 loops, best of 3: 468 usec per loop
&amp;gt;&amp;gt;&amp;gt; python -mtimeit -s&#39;import random&#39; &#39;x=range(1000); random.shuffle(x); sorted(x)&#39;
1000 loops, best of 3: 674 usec per loop
&amp;gt;&amp;gt;&amp;gt; python -mtimeit -s&#39;import random&#39; &#39;x=range(1000); random.shuffle(x); x.sort()&#39;
1000 loops, best of 3: 662 usec per loop
&lt;/pre&gt;
&lt;p&gt;總體來說，雖然兩者區別不大，功能也幾乎完全相同 (後面會詳細說明)，但如果只想記一種使用方式，我會更推薦使用 sorted 函式，除了 sorted 函式更加通用外，串列 (list) 提供的 sort 函式也比較容易誤用，比如說：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sorted_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 錯誤！ sort 函式沒有回傳值&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;雖然我們了解沒有回傳值是為了避免混淆才這麼設計的，可是不熟悉的人還是很容易忘記這一點，常常會浪費許多時間在處理這種問題。所以我認為在沒有特殊需求時，使用 sorted 函式就行了。&lt;/p&gt;
&lt;p&gt;當然，並不是說不能使用 sort 函式，我們還是可以視情況使用，但使用時不要忘記－－sort 函式會直接修改串列的內容，所以要確定原始的串列內容不需要保留才行。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;注意！一個是 sorted，一個是 sort，拼法不同喔～&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;此外，這兩個函式還提供了一些參數來處理更複雜的排序問題。這裡我不分別說明兩者可接受的參數為何，因為除了sort 函式不需要接受序列當參數外，其餘參數完全相同，均有「reverse」、「cmp」和「key」三個參數可以使用。詳細怎麼使用呢？隨後會一一介紹：&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;reverse&quot;&gt;
&lt;h3&gt;參數 reverse&lt;/h3&gt;
&lt;p&gt;此參數很容易由字面上理解，如果輸入布林值 True，就會反轉排列的順序，原本由小排到大，就會變成由大排到小：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grades&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;70&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grades&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reverse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;70&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;cmp&quot;&gt;
&lt;h3&gt;參數 cmp&lt;/h3&gt;
&lt;p&gt;有時，一些特殊序列裡的元素可能無法直接比較大小，或著我們想要用自己的方式比較大小。比如說班上的同學有男有女，若把女生的頭扭下來，男女比可能就會變得不太漂亮。這時我們可以新增一個條件－－如果男生和女生比較的話，女生無條件勝出！碰到這種「現實社會碰到的複雜難題」，單純比較大小已經無法適用的情況，我們就可以使用參數 cmp。&lt;/p&gt;
&lt;p&gt;透過這個參數，我們可以自行決定序列元素比較大小的方式(如女生一定比男生大)，只要指定一個「比較函式」給參數 cmp，那麼排序函式運行時，就會利用這個「比較函式」決定兩個元素之間的大小。&lt;/p&gt;
&lt;p&gt;這個「比較函式」可以接受兩個元素當參數，並回傳兩者比較大小後的結果，如果前者比後者大，就會回傳正數；前者比後者小，就會回傳負數；兩者相等，則會回傳零。我們可以自行定義之。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 自定的比較函式&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;student1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;女生&amp;#39;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;垃圾&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 正數 student1 &amp;gt; student2&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;垃圾&amp;#39;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;女生&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# 負數 student1 &amp;lt; student2&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;女生&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;垃圾&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;女生&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;70&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;垃圾&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reverse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;執行結果如下：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
90 女生
70 女生
80 垃圾
60 垃圾
&lt;/pre&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;此例其實不用這麼麻煩，因為「女生」注定在「垃圾」之上，所以其實不需要參數 cmp，直接排序就好了，不服氣的話可以試試！&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;(其實是因為 ASII 排序的關係)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;key&quot;&gt;
&lt;h3&gt;參數 key&lt;/h3&gt;
&lt;p&gt;有時我們會碰到一種特殊情形－－我們想比較的不是序列的元素本身，好比說班上的排名，我們雖然是對學生做排序，但我們為學生排名時，真正想比較的不是學生本身，而是學生的成績。&lt;/p&gt;
&lt;p&gt;碰到這種情況，有一種很常見的解決方式就是找出原序列元素的真正想比較的內容（如學生的分數）建立一個輔助序列，這個輔助序列與原序列的每一個元素可以一對一對應。接下來，只要排序這個輔助序列，我們就能知道原序列相應要如何排序了。&lt;/p&gt;
&lt;p&gt;實際操作時，我們通常會為利用元組(tuple) 來達成這個目的：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Student&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__repr__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;repr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Student&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;小兜&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;垃圾&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Student&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;小雞&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;女生&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Student&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;小蟲&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;垃圾&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Student&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;小喵&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;70&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;女生&amp;#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#輔助序列，內容為 [(80, &amp;#39;小兜&amp;#39;), (90, &amp;#39;小雞&amp;#39;), (60, &amp;#39;小蟲&amp;#39;), (70, &amp;#39;小喵&amp;#39;)]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;decorated_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;student&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;元組 (tuple) 排序時會由第一個先排，因此我們可以直接排序這個輔助序列。接下來，照順序把元組拆開，只留原本資料的部分，就是排序好的序列了。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sorted_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decorated_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reverse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;原理非常簡單，但實作起來非常麻煩。&lt;/p&gt;
&lt;p&gt;所以在 Python 2.4 之後，引入了參數 key。與參數 cmp 相同，key 也可以接受一個函式當做參數。這個函式的功能是輸入一個元素，然後回傳這個元素的「代理」，比如說下面的例子：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sorted_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reverse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#輸入 student 回傳 grade&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;原理和剛剛手動操作的方式相同，只是現在 Python 自動幫我們解決了。&lt;/p&gt;
&lt;p&gt;順帶一提，Python 在 operator 這個模組提供了一些內建函式來協助我們定義 key 可以使用的自訂函式，舉例來說：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;operator&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# operator.attrgetter(&amp;#39;grade&amp;#39;) 等同於 lambda student: student.grade&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reverse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrgetter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;grade&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;除了 attrgetter 函式外，這個模組中還包含了許多東西，像是 itemgetter 函式，用途應該不難猜，所以我就不多提了。事實上，我並不推薦這種使用方式，因為這並不會減少程式碼多少，也不會增加什麼效能，何必付出額外的代價學習記憶呢？是故相對之言，我更加推薦簡潔通用的「lambda」。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;cmp-v-s-key&quot;&gt;
&lt;h3&gt;參數 cmp v.s. 參數 key&lt;/h3&gt;
&lt;p&gt;看到這裡，你可能已經發現這兩個參數的功能似乎有重疊之處，事實上也確實是如此。&lt;/p&gt;
&lt;p&gt;不過若要直接比較使用那一個比較好，似乎也不太妥當，因為這兩者的工作並不相同，而且也不是互斥的。參數 cmp 是決定「元素之間比較的方式」，而參數 key 則是為每一個元素「找一個代理來比較」。回傳的代理也可再交由 cmp 函式來比較兩者的大小，兩者並沒有衝突的地方。&lt;/p&gt;
&lt;p&gt;不過話說回來，因為參數 key 的功能確實幾乎能取代參數 cmp，而且一般來說使用參數 key 的效能會比較好，因為一個元素可能會呼叫很多次參數 cmp 的比較函式，但最多只會呼叫 key 的函式一次。另一方面，在 Python 3 以後，也取消 cmp 這個參數了。基於以上理由，雖然目前在 Python 2.x 中仍可以使用參數 cmp，但我認為還是盡可能改用參數 key 比較保險。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h2&gt;進一步討論&lt;/h2&gt;
&lt;div class=&quot;section&quot; id=&quot;python-2&quot;&gt;
&lt;h3&gt;Python 排序用的演算法&lt;/h3&gt;
&lt;p&gt;Python 使用一種叫 Timsort 的混種排序演算法，這是由 Tim Pepters 這位大神為 Python 設計的，在 Python 2.3 後成為了 Python 的標準演算法。&lt;/p&gt;
&lt;p&gt;Timsort 融合合併排序 (Merge Sort) 和插入排序 (Insertion Sort) 兩種排序演算法。個數少用就是插入排序，個數多則用合併排序。差別是這個合併排序有點不一樣，裡面引用了一個簡單的概念增加排序的效果，其概念是「在現實情況中，大部分的序列裡面會藏有部分早就排序好的小片段，由於這些小片段不需要再花時間排序，所以抓出這些小片段就可以減少排序的時間」，我們也不需要知道這麼詳細，不過有一點一定要清楚，那即是這種排序法是一種「穩定」的排序法，也就是說這個演算法會保證維持相等值的相對次序，比如說：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Student&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;小雞&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;女生&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Student&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;小喵&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;女生&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;小雞&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;小喵&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;因為小雞和小喵的兩人的分數相同，所以如果一開始小雞在小喵之前，排序完小雞一定還會在小喵之前。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h3&gt;什麼可以排序？&lt;/h3&gt;
&lt;p&gt;首先，自然要是序列才能排序，之前在「&lt;a class=&quot;reference external&quot; href=&quot;/articles/淺談-python-的-for-迴圈/&quot;&gt;淺談 Python 的 for 迴圈&lt;/a&gt;」有提到，至少要支援 __iter__ 或 __getitem__ 這兩個函式才能當成序列，若要使用自訂的序列，必須要考慮到這一點。&lt;/p&gt;
&lt;p&gt;另一方面，序列裡面的元素也有限制，不想用參數 cmp 的話，裡面元素就要能直接比較大小。&lt;/p&gt;
&lt;p&gt;可是現在有一個小問題，那就是比較大小相關的函式有 __lt__ (小於) 、__gt__ (大於) 、__eq__ (等於) 、__ne__ (不等於) 、__le__ (小於或等於) 、__ge__ (大於或等於) 六種。如果只為了排序，就要另外定義這六個函式也是挺麻煩的，不是嗎？&lt;/p&gt;
&lt;p&gt;幸好我們不用擔心這一點，因為 Python 保證了只要有定義 __lt__ 函式，那麼排序的時候就只會用 __lt__ 函式；而如果沒有定義 __lt__ 函式，至少也只會用 __gt__ 函式，換言之，實作 __lt__ 函式足矣。&lt;/p&gt;
&lt;p&gt;話說回來，當碰到需要自行定義比較大小時，定義全部六個比較函式顯然還是比較推薦的做法。但正如前面所說的，我們其實不想單為了排序就要額外實作六個函式，所以 Python 2.7 以後提供了一個好用的解決方式，那就是使用 functools 模組的 total_ordering。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@total_ordering&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Student&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
       &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;
       &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;
       &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__eq__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;              &lt;span class=&quot;c1&quot;&gt;# 僅需要實作兩個函式即可&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__lt__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;student&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__repr__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;具體的做法就是在自訂的類別上面加上 &amp;#64;total_ordering，接下來只需要實作兩個比較函式即可，其中一個限定是 __eq__ 函式，至於另外一個，則可以自由選擇 __lt__ 函式、__le__ 函式、__gt__ 函式或 __ge__ 函式實作。&amp;#64;total_ordering 就會自動幫我們補完剩下的函式。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;雖然可以四選一，但我覺得實作 __lt__ 函式會是比較好的選擇。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;natural-sort&quot;&gt;
&lt;h3&gt;特殊應用 – Natural Sort 的問題&lt;/h3&gt;
&lt;p&gt;還有一個問題也常碰到，那就是 Natural Sort 的問題，舉例來說，假設有一些散落的書頁，而我想做排序：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;p14&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p3&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p13&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p2&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p4&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p12&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p11&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p1&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;p1&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p11&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p12&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p13&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p14&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p2&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p3&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p4&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;咦！為什麼 p11 會排在 p2 前面？這其中的奧妙自然是 Python 使用 ASII 的方式排序。&lt;/p&gt;
&lt;p&gt;但重點是該怎麼解決這個問題呢？我沒有找到一個官方的解決辦法。但幸好 Python 擁有強大的第三方函式庫可以使用，所以我們可以選用 natsort 來解決這個問題，這個函式庫可以簡單的用 easy_install 或是 pip 下載。&lt;/p&gt;
&lt;p&gt;直接舉個簡單的使用範例：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;natsort&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;natsorted&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;natsorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;p1&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p2&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p3&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p4&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p11&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p12&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p13&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;p14&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;便可以輕鬆解決這個問題了。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-3&quot;&gt;
&lt;h2&gt;總結&lt;/h2&gt;
&lt;p&gt;學完了 Python 排序相關的使用方法後，也許你現在手癢癢想要試試各種操作 Python 排序的方式，不過小弟我在這裡要提醒一件事－－別走火入魔了，很多事情並不需要使用排序！
這裡做一個測試－－假設這裡有一個序列，目標是找出最大的元素，你心中第一個想到的是什麼呢？&lt;/p&gt;
&lt;div class=&quot;line-block&quot;&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;如果是 sorted 的話，那麼恭喜你走火入魔了XD。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[淺談 Python 的 for 迴圈]]></title>
        <id>https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-python-%E7%9A%84-for-%E8%BF%B4%E5%9C%88/</id>
        <link href="https://blog.marco79423.net/articles/%E6%B7%BA%E8%AB%87-python-%E7%9A%84-for-%E8%BF%B4%E5%9C%88/"/>
        <updated>2013-10-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[
Note
本文以 Python 2 為例。


for 迴圈的基本運作
為何會需要迴圈呢？
因為世上有許多相似甚至完全相同的事情，而我卻不想手動一一列舉，好比在螢幕上依序印出二十六個英文字母。如果仔細分析，便可發現在印出的過程中，除了字母本身不同外，其餘皆完全相同 ...]]></summary>
        <content type="html"><![CDATA[&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;本文以 Python 2 為例。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;for&quot;&gt;
&lt;h2&gt;for 迴圈的基本運作&lt;/h2&gt;
&lt;p&gt;為何會需要迴圈呢？&lt;/p&gt;
&lt;p&gt;因為世上有許多相似甚至完全相同的事情，而我卻不想手動一一列舉，好比在螢幕上依序印出二十六個英文字母。如果仔細分析，便可發現在印出的過程中，除了字母本身不同外，其餘皆完全相同。但要我為每個字母都寫一遍「print」，不是太麻煩了嗎？&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 顯示所有英文字母&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;A&amp;quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;B&amp;quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Z&amp;quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 重覆寫 26 遍 print 太麻煩！&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;此外，許多時候「需要重覆幾次」，得在實際運行時才知道。舉例來說，我需要從資料庫抓取所有使用者的名稱來顯示，但在撰寫程式碼時，我並不會預先知曉總共有幾位使用者，因此即使我真想手動輸入「print」也做不到。&lt;/p&gt;
&lt;p&gt;這時 for 迴圈便可登場了！套用上例，我們可先將抓出來的使用者先存進 name_list 串列中，再由迴圈將所有名稱顯示一遍：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;「for」和「in」是 Python 的關鍵字，兩者之間可以放置使用者自訂的變數，而「in」後則可接一個序列 (Sequence)，串列 (list)、字串 (str)、元組 (tuple) 等皆是序列的一種。&lt;/p&gt;
&lt;p&gt;迴圈會依序從序列取得元素，並將元素指定給前面自訂的變數(此例為 name)，再執行迴圈裡的內容，直到序列每一元素都被取出過為止。&lt;/p&gt;
&lt;p&gt;這裡插播一個 Python 的小用法，Python 可以用下面的方式指定變數：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 這裡 item 會等於 (&amp;quot;小雞&amp;quot;, 100)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 也可以這麼做&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 這裡 name 會等於「小雞」，grade 會等於「100」&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 括號本身也可以省略&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 結果於上一句相同&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這種用法在 for 迴圈指定變數時也適用，序列裡的元素也是可以被拆解並直接指定對應的變數，因此可以寫得像下面這樣：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grade_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;兩大類&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小喵&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小蟲&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 可以直接改寫&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 或是這樣&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grade&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;想一想：這裡是使用元組拆解，那麼如果是串列可不可以呢？&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;很多讀者想必已經發現 python 的迴圈其實就是其他語言 foreach 的用法。與其他語言不同，其他語言的 for 使用邏輯通常是「找出序列所有可能的索引 (index)，再透過索引取得序列對應的資料」。碰到上面的情況，C++ 的使用者通常會這麼做：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果要在 Python 模擬這個概念，就會像這樣：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;range 函式會依據參數回傳一個整數數列，假設輸入 10 便會回傳 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;明顯麻煩許多，顯然 Python 更希望我們以 foreach 的用法取代其他語言使用 for 的方式。&lt;/p&gt;
&lt;p&gt;為何如此？&lt;/p&gt;
&lt;p&gt;道理很簡單，比較兩種方式，可以發現 foreach 的用法明顯擁有更高的可讀性。在此例中，用變數 name 來表示名稱顯然比用 name_list[i] 還要直覺。更重要的是在大多數情況下，我們其實根本就不需要索引的資訊，何必多此一舉，先找到索引，然後才取得序列的元素內容呢？&lt;/p&gt;
&lt;p&gt;話說回來，碰到需要索引資訊的情況又該如何？難道又得回到之前可讀性差的做法嗎？&lt;/p&gt;
&lt;p&gt;不需要。&lt;/p&gt;
&lt;p&gt;因為 Python 提供了 enumerate 函式，可以漂亮地解決該問題，此函式可以接受一個序列當參數，並回傳一個新序列，新序列的每一元素都是一個元組，包含了一個連續整數和原來的元素，就像下面這樣：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;兩大類&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;小喵&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;小蟲&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;小雞&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;兩大類&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;小喵&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;小蟲&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 實際上是一個 generator ，這只是為了方便解釋。&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這樣我們就可以在保持可讀性的情況下解決問題：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;此外 Python 的 for 迴圈還有一個異於其他語言的特殊用法，那就是可以使用關鍵字「else」：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;以上就是所有的名稱&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;當序列所有的元素都被取出，進行完最後一次迴圈後，便會執行 else 裡的內容。舉例來說，上面例子的結果會顯示：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
小雞
兩大類
小喵
小蟲
以上就是所有的名稱
&lt;/pre&gt;
&lt;p&gt;或許你會好奇，既然最後才會執行，為何不直接放到迴圈外呢？&lt;/p&gt;
&lt;p&gt;因為這還可以搭配關鍵字「break」使用，只要使用 break 中途跳出迴圈的話，就不會執行 else 的內容。&lt;/p&gt;
&lt;p&gt;我們用「找質數」來舉例：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#找 0 ~ 100 所有的質數&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;是一個質數&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;關鍵字 continue 不算是跳出迴圈，所以就算在最後一圈使用 continue，還是會執行 else 裡的內容。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;嚴格說來，通常真要丟到迴圈外處理也不難。此例來說，由於 Python 在迴圈指定的變數(此例是 num)，在迴圈結束後還會保留，所以我一樣可以判斷迴圈是不是進行到最後一圈才結束：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;是一個質數&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我認為 else 最大的意義在於其擁有比較好的可讀性，可以輕易的展現 else 裡的內容和迴圈的關聯性。相對來說，上面的程式碼就比較難一眼看出 if 和前面迴圈的關係。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;不過這點可能見仁見智，對於其他語言轉過來的人而言，這種做法可能反而增加閱讀的困難，所以還是得看場合使用。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;還有一點要注意－－在 Python 中並不是所有型態的序列都可以在 for 迴圈運行時新增或刪除元素，因為這個動作有潛在的風險，所以有些型態直接限制了這項操作。&lt;/p&gt;
&lt;p&gt;怎麼說呢？&lt;/p&gt;
&lt;p&gt;我們可以反過來看，像是串列准許你在迴圈中新增或刪除元素的類型，其使用上可能會有什麼問題。&lt;/p&gt;
&lt;p&gt;簡單來說，你可以想像在迴圈運作時，會有一個計數器紀錄迴圈進行的圈數，每做一次迴圈，計數器就會加一。透過這個計數器，程式就會知道接下來要處理的是那一個變數。&lt;/p&gt;
&lt;p&gt;這時我們在中途新增或刪除元素時，由於計數器的值沒有改變，那麼藉由「原來的計數器」取出「新的序列」的元素，自然就會出錯。比如說：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;num_list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_list&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;運行結果，你可能會以為是：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
1
2
3
[]
&lt;/pre&gt;
&lt;p&gt;但實際上會是：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
1
3
[2]
&lt;/pre&gt;
&lt;p&gt;當然，要處理這個問題也不困難，只要我們複製一份暫存的版本就行了：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:]:&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;num_list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_list&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這樣一來，修改不會影響暫存的版本，自然就不會出錯了。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h2&gt;實作一個可以被當成序列的物件&lt;/h2&gt;
&lt;p&gt;在 Python 中，如果物件有實作 __getitem__ 函式或 __iter__ 函式時就能被當成序列。
但如果兩個函式都有實作，那麼 Python 會先嘗試呼叫  __iter__ ，如果發現沒有實作這個函式時，才會呼叫 __getitem__。&lt;/p&gt;
&lt;p&gt;首先來介紹 __getitem__ 函式，這函式的意義是讓物件可以用 object[index] 這種方式取得資料。我們可以輸入索引值，然後回傳對應位置的元素。索引值必須從零開始，當超過元素個數時，便會擲出 IndexError 異常：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MySequence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__getitem__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;IndexError&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_element_by_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;只要實作了該函式，就能被當成序列讓 for 迴圈處理，其運作過程感覺就像下面這樣：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
1. 取得 my_sequence[0] 當作元素，然後執行迴圈裡面的內容
2. 取得 my_sequence[1] 當作元素，然後執行迴圈裡面的內容
3. 取得 my_sequence[2] 當作元素，然後執行迴圈裡面的內容
4. ...(不斷重覆，直到嘗試取得 my_sequence[n] 時發生 IndexError 異常)
5. 結束迴圈
&lt;/pre&gt;
&lt;p&gt;但這其實是舊式的做法(說不準未來會不會淘汰的方法)，現在 Python 會比較推薦使用 __iter__ 的方式。&lt;/p&gt;
&lt;p&gt;這種方式迴圈並不會直接與序列溝通，而是間接由一個「迭代器 (iterator)」物件來取得序列的元素。迴圈先利用序列的 __iter__ 取得迭代器，再藉由迭代器的 next 函式取得序列的每一個元素。&lt;/p&gt;
&lt;p&gt;呼叫 next 函式時不需要任何參數，這個函式每次呼叫都會回傳序列的下一個的元素，直到全部回傳過了為止。此時如果再呼叫這個函式，就會擲出 StopIteration 異常，表示序列每個元素都被回傳過了，運行的過程就像下面這樣：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
1. 呼叫 my_sequence 的 __iter__ 函式取得迭代器
2. 呼叫迭代器的 next 函式取得序列元素，然後執行 for 迴圈裡面的內容
3. 呼叫迭代器的 next 函式取得序列元素，然後執行 for 迴圈裡面的內容
4. 呼叫迭代器的 next 函式取得序列元素，然後執行 for 迴圈裡面的內容
5. ...(不斷重覆，直到發生 StopIteration 異常)
6. 結束迴圈
&lt;/pre&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;如果發生 StopIteration 異常後，又再一次呼叫 next 會發生什麼事情呢？ 會－－繼續賞你一個 StopIteration 異常。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;簡單來說，我們必須弄出一個迭代器給序列的 __iter__ 回傳。要實作一個迭代器必須完成兩個條件，一是實作前文所述的 next 函式，二是實作屬於迭代器的 __iter__。不過迭代器的 __iter__ 只需要回傳自己 (self) 即可，這是因為 Python 希望迭代器本身也要能進行迴圈。換言之，即使不實作迭代器的 __iter__ 也沒關係，所屬的序列還是可以進行迴圈。&lt;/p&gt;
&lt;p&gt;實作的結果可能會像下面這樣：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;# 序列的 __iter__ 函式必須回傳一個迭代器&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MySequence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__iter__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MyIterator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MyIterator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# 就算不實作此函式，MySequence 還是可以迴圈&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;fm&quot;&gt;__iter__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;StopIteration&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_element_by_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;至於這裡為何是 StopIteration 異常而不是 IndexError 異常，理由是為了避免 next 函式真的發生 IndexError 而無法判斷。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;簡單來說，一個物件要能被當作序列使用，就必須實作 __getitem__ 或是 __iter__ 函式。&lt;/p&gt;
&lt;p&gt;但說真的，自己實作迭代器其實也是挺麻煩的，有沒有辦法可以簡單的產生迭代器呢？有的，那就是使用 yield。不過因為受限於篇幅的原因，所以這裡不討論它的詳細用法，有興趣的可以自己去查相關資料：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;iterator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iterator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;除外，有時我們也可能會碰到「感覺上很適合給 for 迴圈使用」的函式，這種函式的行為很像迭代器，可以不斷吐出一個個元素。一個很經典的例子就是檔案物件 (file object) 的 readline 函式，這個函式可以一行行讀出檔案的內容，感覺上就像是迭代器一個個吐出元素一樣。但因為這是一個函式，而不是迭代器，所以不能給 for 迴圈使用。&lt;/p&gt;
&lt;p&gt;碰到這種情況，我們可以用 iter 函式來幫助我們，這個函式可以為我們「包裝」成一個迭代器來使用，其主要有兩種用法，第一種用法是輸入一個物件當參數，然後這個函式會直接呼叫該物件實作的 __iter__ 函式的結果當回傳值。&lt;/p&gt;
&lt;p&gt;第二種用法就是我要提的，我們可以輸入兩個參數給這個函式，第一個參數是所要執行的函式，第二個參數則是迭代器中止的條件，其中如果函式回傳的結果和第二個參數的值相等，就會擲出 StopIteration 異常，因此上述的例子就可以這麼做：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;我的檔案.txt&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;readline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;是不是很簡單呢？&lt;/p&gt;
&lt;p&gt;淺談完畢，謝謝看完的各位。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[初探 Mercurial 之女友開發日誌]]></title>
        <id>https://blog.marco79423.net/articles/%E5%88%9D%E6%8E%A2-mercurial-%E4%B9%8B%E5%A5%B3%E5%8F%8B%E9%96%8B%E7%99%BC%E6%97%A5%E8%AA%8C/</id>
        <link href="https://blog.marco79423.net/articles/%E5%88%9D%E6%8E%A2-mercurial-%E4%B9%8B%E5%A5%B3%E5%8F%8B%E9%96%8B%E7%99%BC%E6%97%A5%E8%AA%8C/"/>
        <updated>2013-09-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[「身為一個仁義正直、年輕有為的宅男而言，想要一個開源免費的『女友』，當然是一件很正常的事情！」
「當全世界的女人都發現不了你的優點時，能夠自行創造現想中的美好，一個純真、可愛、又對你百依百順的女朋友，是多麼美妙的事情呢？」
所以說怎麼辦？開工啊！
我現在就來開發一個程式專案－－「女友 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;&lt;em&gt;「身為一個仁義正直、年輕有為的宅男而言，想要一個開源免費的『女友』，當然是一件很正常的事情！」&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;「當全世界的女人都發現不了你的優點時，能夠自行創造現想中的美好，一個純真、可愛、又對你百依百順的女朋友，是多麼美妙的事情呢？」&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;所以說怎麼辦？開工啊！&lt;/p&gt;
&lt;p&gt;我現在就來開發一個程式專案－－「女友」吧！&lt;/p&gt;
&lt;p&gt;(並不代表小弟我就是宅男)&lt;/p&gt;
&lt;p&gt;這顯然是一個大工程，畢竟女友這種生物，沒有最完美，只有更完美！&lt;/p&gt;
&lt;p&gt;為了達成我們遠大的夢想，一個稱手的好工具，自然是不可或缺的。&lt;/p&gt;
&lt;p&gt;我們想像一個情境－－開發的過程中，免不了需要新增、修改一些原始碼。我們可能會為了安全起見，先將檔案複製一份，然後命名成「xxxx.orig」或「xxxx.old」之類的名稱，以免未來出狀況時找不到補救的機會。&lt;/p&gt;
&lt;p&gt;這種做法雖然可用，但缺點是會產生很多備份檔，若真要回復時，時常反而找不到所需要的檔案。而且通常備份檔之間的內容差異很小，大部分都是重覆的內容。換言之，有很多硬碟空間就這樣白白浪費在這些重覆的內容裡了。&lt;/p&gt;
&lt;p&gt;因此，有些人會特意限制備份檔的數量，超過就刪除。但問題是雖然刪除的備份檔都是當下認為不重要的，但未來會不會這麼想可就不好說，萬一未來什麼時候改變主意，檔案也已經救不回來了。&lt;/p&gt;
&lt;p&gt;幸好有一個很好用的系統可以幫我們解決這個問題－－那就是 Mercurial。這是一種版本控制系統，這個系統可以有效地幫助我們解決備份檔案的問題。&lt;/p&gt;
&lt;p&gt;Mercurial 能夠紀錄檔案的變更，省去手動備份的麻煩。而且好處是目錄裡再也看不到一堆名稱奇怪的備份檔了！因為 Mercurial 會偷偷將這些備份存在我們看不見的地方。&lt;/p&gt;
&lt;p&gt;只要指定好 Mercurial 的「工作目錄(working directory)」，它就能幫我們檢查該目錄裡檔案的變更，並在需要的時候幫我們備份裡頭的內容。這個工作目錄沒什麼特別，就只是普通的目錄而已，隨便你指定都行。&lt;/p&gt;
&lt;p&gt;至於 Mercurial 究竟會將備份存到那裡了呢？其實只要仔細觀察，就能發現 Mercurial 會在工作目錄裡偷偷新增「.hg」的隱藏目錄，此目錄正是專案的「版本庫(其實英文是 repository，不過我覺得翻成版本庫比較直覺)」，Mercurial 會把所有備份通通存放在這裡。&lt;/p&gt;
&lt;p&gt;不過我們其實可以不用理會，只需要知道 Mercurial 的工作目錄裡面一定會有這個目錄即可。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/23443cf8-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/23443cf8-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;假設我正在開發「女友的眼睛.py」，並且不斷修改裡面的內容。而我為了避免出問題，所以每修改到一點便要求 Mercurial 備份。當裡頭內容為「女友的眼睛」時備份一次，改成「女友的小眼睛」時又備份一次，然後又改成「女友清澈的小眼睛」時又再備份一次。&lt;/p&gt;
&lt;p&gt;這三種版本的「女友的眼睛.py」都會存在工作目錄裡的版本庫中，只要我有這個需要，我可以在工作目錄中輕鬆切換三種版本的「女友的眼睛.py」。&lt;/p&gt;
&lt;p&gt;Mercurial 其實並不會真的備份三種版本的「女友的眼睛.py」，這樣一來就和傳統的做法沒什麼分別了，我前面何必廢話這麼多？事實上，Mercurial 只會儲存三個版本的「差異」而已，並不會備份重覆的內容。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2344b2fa-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2344b2fa-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;div class=&quot;section&quot; id=&quot;mercurial-1&quot;&gt;
&lt;h2&gt;Mercurial 的基本操作&lt;/h2&gt;
&lt;p&gt;Mercurial 的所有指令都是由「hg」開頭，這是參考自素週期表的水銀而取的名字。後面會將一些常用的功能一一介紹，若想知道更詳細的資訊，可以透過下列指令查詢：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg help 關鍵字 # 關鍵字如：init, add, remove,...
&lt;/pre&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-version&quot;&gt;
&lt;h3&gt;hg version&lt;/h3&gt;
&lt;p&gt;若是第一次在主機使用的話，可以先用下列指令查看主機裡 Mercurial 的版本，順便檢查 Mercurial 有沒有正確地安裝。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg version
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-init&quot;&gt;
&lt;h3&gt;hg init&lt;/h3&gt;
&lt;p&gt;等到確定 Mercurial 有正確安裝後，就可以建立新專案了，假設我決定將這個專案取名為 「girlfriend」，我會這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg init girlfriend
&lt;/pre&gt;
&lt;p&gt;init 後面接的是專案名稱，下完指令後，當前目錄就會多了一個名為「girlfriend」的新目錄，這即是先前提過的「工作目錄」。之後的工作都要在此進行，我們可以在裡頭建立檔案、修改檔案內容、建立子目錄等。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;Mercurial 會根據專案名稱建立一個新的目錄，即為「工作目錄」；若不加上專案名稱，就不會建立新目錄，而是把當前目錄當成「工作目錄」。&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;專案都是個自獨立的，每個專案都會有屬於自己的「.hg」，原則上不要去修改此目錄的內容。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-add&quot;&gt;
&lt;h3&gt;hg add&lt;/h3&gt;
&lt;p&gt;不是每一次變動檔案都要做紀錄，也不是每一個檔案都有紀錄的價值，所以 Mercurial 的可以讓讓使用者自行決定那些檔案需要追蹤。就算是要追蹤的檔案，
也不是每一次變動值得紀錄，若每修改一個字就備份一次，只會產生一堆無意義的版本。所以 Mercurial 交由使用者自己決定何時「提交」最新的版本給 Mercurial 紀錄。&lt;/p&gt;
&lt;p&gt;每提交一次，版本庫就會多一個版本，Mercurial 會將整個工作目錄中有追蹤的內容紀錄下來。回復時，其實就是將工作目錄的內容變回其中一次提交時工作目錄的狀態。&lt;/p&gt;
&lt;p&gt;因此在專案開發時，我必須決定那些檔案需要追蹤，那些不需要。假設我決定要追蹤「女友本體.py」，那麼我可以這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg add 女友本體.py
&lt;/pre&gt;
&lt;p&gt;這樣在下次提交時，Mercurial 除了會把「女友本體.py」存進版本庫外，還會追蹤這個檔案在不同次提交時的內容變動。此後每一次提交時，只要檔案的內容和上一次提交時的內容不同，就會將新的修改儲存進版本庫裡。&lt;/p&gt;
&lt;p&gt;並不是下完 hg add 後就會馬上將該檔案存進版本庫裡，而是等到提交時才會這麼做。此後也不用再使用 hg add 這個檔案，因為 Mercurial 會自動追蹤檔案後續的變化。&lt;/p&gt;
&lt;p&gt;如果我想新增工作目錄所有未追蹤的檔案，我可以選擇不加上檔案名稱，那麼 Mercurial 就會直接追蹤工作目錄所有未追蹤的檔案。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg add
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-remove&quot;&gt;
&lt;h3&gt;hg remove&lt;/h3&gt;
&lt;p&gt;有新增自然就有刪除，我們當然也可以要求 Mercurial 取消對某一個檔案的追蹤，舉例來說，我打算刪除「女友的腳.py」，我可以這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg remove 女友的腳.py  # remove 可以用 rm 取代
&lt;/pre&gt;
&lt;p&gt;這樣就會刪除工作目錄中的「女友的腳.py」，並且取消這個檔案的追蹤。若不透過這個方法刪除的話，Mercurial 還是會繼續追蹤這個暫時「丟失」的檔案。&lt;/p&gt;
&lt;p&gt;要知道 Mercurial 只會紀錄變動，所以當你沒有明確告訴它：「這個檔案被刪掉了喔！」，那麼 Mercurial 就仍會當這個檔案存在，並且一直保持在最後一次變動的版本。也就是說對 Mercurial 而言，「丟失」不算變動，就只是丟失了而已。&lt;/p&gt;
&lt;p&gt;但若真的忘記用 hg remove 刪除檔案了，也可以在事後補上：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg rm --after 欲刪除的檔案 #--after 可以用 -A 代替
&lt;/pre&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;不過就算不加上 --after 其實也關係。&lt;/p&gt;
&lt;/div&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2343976c-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2343976c-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;hg remove 並不會刪除歷史，所以這個檔案還會存在於先前的版本中。所以雖然「女友的腳.py」被我刪掉了，但這個檔案仍然會存在於之前的版本中。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-status&quot;&gt;
&lt;h3&gt;hg status&lt;/h3&gt;
&lt;p&gt;在每次提交之前，最好都要先用 hg status 檢查一下在上次提交後究竟做了那些變動，順便檢查是不是有什麼檔案忘記用 hg add 新增：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg status  #status 可以用 st 代替
&lt;/pre&gt;
&lt;p&gt;然後螢幕就會顯示類似下方的列表：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
M 女友本體.py
A 女友的眼睛.py
R 女友的腳.py
? 女友的嘴.py
...
&lt;/pre&gt;
&lt;p&gt;簡單解釋一下其中的意思，每行開頭字元表示變動的類型，「M」代表上次提交後，檔案有被修改過，「A」則代表新增追蹤的檔案，「R」則代表取消追蹤的檔案。&lt;/p&gt;
&lt;p&gt;換言之，如果我們用 hg add 追蹤檔案，就會出現「A」，用 hg remove 刪除檔案就會出現「R」。&lt;/p&gt;
&lt;p&gt;至於「!」是檔案丟失了，表示該檔案並沒有用 hg remove 刪除，而「?」則是這檔案案並沒有被追蹤。&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;16%&quot; /&gt;
&lt;col width=&quot;84%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;檔案已被修改過&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;新增追蹤的檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;取消追蹤的檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;!&lt;/td&gt;
&lt;td&gt;丟失的檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;未被追蹤的檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-move&quot;&gt;
&lt;h3&gt;hg move&lt;/h3&gt;
&lt;p&gt;有時檔案並不是被刪除，只是改名或是移動位置而已，像是將「吃飯功能.py」改名為「不重要的功能.py」，那麼對 Mercurial 來說，就會覺得是丟失了「吃飯功能.py」，並且多了一個未追蹤的檔案「不重要的功能.py」。所以說，無論是改名還是移動位置，我們都必須透過 Mercurial 提供的指令操作才行。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg move 來源檔案 目的檔案  # move 可以用 mv 或 rename 代替
&lt;/pre&gt;
&lt;p&gt;不過由於用法和 Unix 的指令 mv 基本相同，所以我就不多說了。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-commit&quot;&gt;
&lt;h3&gt;hg commit&lt;/h3&gt;
&lt;p&gt;在經過一連串的開發後，如果覺得已經到一個段落了，就可以將當前工作目錄的狀態提交給版本庫。對使用者而言，「提交」就像是將整個工作目錄的內容複製一份到版本庫裡。理論上，因為不會只提交一次，所以提交時必須留下可供辨識的註解，比如說「這是我的第一個版本」、「這是我女友長出眼睛的版本」、「這是我女友腳刪掉的版本」之類的訊息以協助自己了解不同版本之間的區別，這樣之後回復時才知道要選那一個版本。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;養成一個習慣－－每次提交前最好先用 hg status 查看有沒有問題後，才進行提交的動作。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;提交的指令如下：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg commit -u 兩大類 #commit 可用 ci 取代
&lt;/pre&gt;
&lt;p&gt;「-u」後面接提交者的名字，這是必不可少的，因為專案不見得只會有一個開發者，即使現在確實如此，也難保未來不會有新人加入(說不定就是有很多人想一起開發寶貝女友也不一定)。&lt;/p&gt;
&lt;p&gt;由於 Mercurial 可以支援多人開發，所以一定要列出提交的人是誰，這樣出事的時候，才可以找到「正確」的人「修理並處理掉」。&lt;/p&gt;
&lt;p&gt;下完這個指令後，Mercurial 會打開一個編輯器要求為這次的變更提供註解。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2343cc1e-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2343cc1e-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;註解的第一行很重要，因為在一般情況下，Mercurial 只會顯示第一行。因此第一行的訊息最好要能完整的表達出該版本的概況。輸入完成後，即可存檔關閉。若想放棄這次提交的話，關閉時不要存檔就行了。&lt;/p&gt;
&lt;p&gt;另外，如果不想要每次提交都打開編輯器一次，可以選擇使用參數「-m」，然後直接接註解。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg ci -u 兩大類 -m &amp;quot;新增 女友本體.py&amp;quot;
&lt;/pre&gt;
&lt;p&gt;不過每次提交時都要加上參數「-u」也是挺麻煩的！對我來說，開發者自然永遠都是「兩大類」。因此若我想省略這個過程，我可以在家目錄中建立一個名為「.hgrc」的檔案，並輸入下面內容。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
[ui]
username=兩大類
&lt;/pre&gt;
&lt;p&gt;這樣從此提交時，我就不用再自報名號了。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;.hgrc 可以用來設定 Mercurial 很多的相關功能，除了可以指定一些預設值外，還可以指定外掛等的功能。&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;這個檔案可以直接建立在家目錄中，表示該使用者的所有專案都可以延用這個設定；也可以建立在專案的工作目錄裡的 .hg 目錄內，這樣就代表這個設定檔只應用在該專案中。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Mercurial 只會追蹤檔案，不能追蹤目錄。在建立一個檔案前，它會先建立檔案路徑缺少的目錄，而在刪除檔案的時候，如果路徑中的目錄變成空目錄， Mercurial 也會一併刪除這個目錄，也就是說不可能單純管理一個完全為空的目錄。&lt;/p&gt;
&lt;p&gt;另外，並不是工作目錄裡的每一個檔案都想要追蹤(不然還要 hg add 幹麻？)，比如說「*.pyc」、「tmp」這類型的檔案，就沒有紀錄的必要。雖然 Mercurial 不會主動紀錄沒用 hg add 追蹤的檔案，但使用 hg status 時，還是會顯示出來。而且還可能隨著時間還變得越來越多，當一堆「?」佔滿了螢幕時，使用者想砸螢幕的可能性就會上升許多。&lt;/p&gt;
&lt;p&gt;為了解決這個問題，可以在工作目錄上建立一個名為「.hgignore」的文字檔，並在這個檔案中條列出那些類型的檔案需要忽略，比如說：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
syntax: glob
*.pyc
.*.swp
output
&lt;/pre&gt;
&lt;p&gt;第一行需要指定檔名比對的方式，比如可以選擇「glob」採用 Shell 風格或是用「regexp」代表用正規表達式比對。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;最好把 .hgignore 列入追蹤的項目，這樣一來更動不但能保存下來，若是和別人一同開發專案時，也能直接共享這個檔案的設定。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-revert&quot;&gt;
&lt;h3&gt;hg revert&lt;/h3&gt;
&lt;p&gt;人總免不了失手，當發現改錯而且改不回來的時候，果斷放棄也是也不錯的選擇。這時我們可以用 hg revert 這個指令將檔案恢復到最近一次提交前的狀態。&lt;/p&gt;
&lt;p&gt;換言之，也就是說當我們投入了 Mercurial 溫暖的懷抱之後，想開發一個新的功能，就可以盡情開發！&lt;/p&gt;
&lt;p&gt;想為女友新增「說話功能」？我可以直接修改「女友的嘴.py」。反正當修改失敗，而且改不回來的時候，直接利用下列指令就可以回復成最後一次提交前的狀態了：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg revert 女友的嘴.py
&lt;/pre&gt;
&lt;p&gt;你可能會發現 Mercurial 除了會把「女友的嘴.py」回復成之前的狀態外，還會額外將新修改的版本(修改失敗的版本)也複製一份，並命名為「女友的嘴.py.orig」。&lt;/p&gt;
&lt;p&gt;之所以這麼做是因為 hg revert 的功能是檔案回復到提交前的版本，也就是說新修改的內容並沒有紀錄在版本庫之中。因為沒有任何備份，所以萬一回復後的版本不如預設的話，仍有補救的機會。&lt;/p&gt;
&lt;p&gt;當然了，雖然這是貼心之舉，但如果每次回復都還要刪除一次備份的話也很麻煩，所以其實可以加上參數「--no-backup」要求 Mercurial 不要做出備份的動作。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg revert --no-backup 女友的嘴.py #--no-backup 可以用 -C 取代。
&lt;/pre&gt;
&lt;p&gt;具體而言這個指令大概有四種用途：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;回復修改的檔案至上次提交時的狀態&lt;/li&gt;
&lt;li&gt;取消用 hg add 檔案&lt;/li&gt;
&lt;li&gt;回復用 hg remove 刪除的檔案&lt;/li&gt;
&lt;li&gt;不是用 hg remove 刪除的檔案，也可以用這個指令回復&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;簡單來說，這個指令就是可以取消在提交後下的所有指令，並回復到提交時的狀態。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-log&quot;&gt;
&lt;h3&gt;hg log&lt;/h3&gt;
&lt;p&gt;不過也有可能在努力了一陣子後，才終於發現也許讓女友擁有「說話功能」正是一件「重大的錯誤」時……已經幾個版本過去了。所以現在的問題是－－我究竟該回到那一個版本呢？&lt;/p&gt;
&lt;p&gt;幸好！我們只要透過查看版本庫的歷史，就可以判斷究竟要回到那一個版本了。至於怎麼查詢呢？可以這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg log
&lt;/pre&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2342cd50-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2342cd50-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;這裡恐怕還是要解釋一下顯示的內容是什麼意思，簡單來說每一個版本都有幾個欄位：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常見的欄位&lt;/strong&gt;&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;33%&quot; /&gt;
&lt;col width=&quot;67%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;changeset&lt;/td&gt;
&lt;td&gt;版本號：十六進制字串&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;標記&lt;/td&gt;
&lt;td&gt;此版本的別名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用者&lt;/td&gt;
&lt;td&gt;提交的開發者&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;日期&lt;/td&gt;
&lt;td&gt;提交的日期&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;提交摘要&lt;/td&gt;
&lt;td&gt;此版本的註解&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;changeset 後面無論是「版本號」還是「十六進制字串」其實都一樣代表這個版本。一般而言我們都可以用「版本號」來表示版本，如上例版本 1 即代表有「女友的腳.py」的版本。&lt;/p&gt;
&lt;p&gt;不過後面會提到，事實上在團隊合作時，可能會發生不同開發者之間同樣版本號卻分別代表不同版本的情形，有可能會發生有別人的版本 3 和自己的版本 3 的內容是不相同的情形。所以說溝通的時候最好以版本號後面的「十六進制字串」來表示版本，因為這種表示法是絕對的、唯一的，同樣的字串表示的一定是同一個版本。&lt;/p&gt;
&lt;p&gt;另外有些版本會有「標記」這個欄位，可以當作是這個版本的「暱稱」，可以自己定義，至於此例的「tip」則是一個特殊的名稱，是 Mercurial 自動給予的，不會固定指向同一個版本，只單純代表最新的版本，也就是可以直接用 tip 來表示最新的版本。&lt;/p&gt;
&lt;p&gt;最後註解的部分，如果你有實驗過的話，就會發現 Mercurial 只會顯示註解的第一行，這也是之前為什麼說第一行最好就要能完整表達這個版本的概況的原因。但若想查看註解的完整內容，就可以加上參數「-v」來顯示：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg log -v
&lt;/pre&gt;
&lt;p&gt;如果覺得每次都要加上「-v」來顯示完整的內容頗麻煩的話，可以在 .hgrc 設定的 [ui] 欄位多加了一行「verbose=True」：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
[ui]
username=兩大類
verbose=True
&lt;/pre&gt;
&lt;p&gt;這樣就會預設顯示完整的註解了。&lt;/p&gt;
&lt;p&gt;話說回來，版本會越來越多，如果全部一次都顯示在螢幕上的話，就會被一大堆訊息淹沒，所以我們可以只顯示個別版本的訊息或顯示少數幾個版本的訊息就好了。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg log -r 1 #只顯示版本 1 的資訊
&lt;/pre&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg log -r 1 -r 3 #只顯示版本 1 和版本 3 的資訊
&lt;/pre&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg log -r 1:3 #只顯示版本 1 到版本 3 的資訊
&lt;/pre&gt;
&lt;p&gt;參數「-r」後面接代表要顯示的版本，其中版本可以用範圍來表示。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-diff&quot;&gt;
&lt;h3&gt;hg diff&lt;/h3&gt;
&lt;p&gt;等等！雖然 hg log 確實可以幫助我們約略了解有那些版本和不同版本之間的變化，得知有的女友有長腳、有的沒有。但是註解寫得清不清楚卻是很重要的關鍵！人都是會偷懶的動物，就算出現：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
修正了幾個 bug
&lt;/pre&gt;
&lt;p&gt;這種註解也不會覺得奇怪，這樣使用 hg log 就看不出來了。&lt;/p&gt;
&lt;p&gt;這時 hg diff 就可以派上用場了！這個指令可以查看不同版本之間檔案內容變化的細節。至於顯示的格式因為就和 Unix 指令中的 diff(Unified format) 一樣，所以我不多解釋。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg diff -r 0 -r 2 #比較版本 1 和版本 2 內容的差別。
&lt;/pre&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
diff -r 2ef6d7097cc8 -r dbe19235ea1e 女友本體.py
--- a/女友本體.py       Tue Aug 19 20:58:05 2013 +0800
+++ b/女友本體.py      Tue Aug 19 21:05:43 2013 +0800
&amp;#64;&amp;#64; -1,1 +1,1 &amp;#64;&amp;#64;
-本體
+修改後的本體
&lt;/pre&gt;
&lt;p&gt;這樣我就可以很清楚的看出來原來是「女友本體.py」被修改過了。檔案裡面的第一行「本體」被改成了「修改後的本體」。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-cat&quot;&gt;
&lt;h3&gt;hg cat&lt;/h3&gt;
&lt;p&gt;除了看檔案在不同版本之間的變化外，其實我們也可以直接查看某一個檔案在某一個版本的內容為何？我們可以利用 hg cat 直接顯示一個檔案在某一個版本的完整內容，如：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg cat -r 1 女友本體.py
&lt;/pre&gt;
&lt;p&gt;參數「-r」後面接的欲查看的版本。這個指令的意思是查看「女友本體.py」在版本 1 時的內容。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-update&quot;&gt;
&lt;h3&gt;hg update&lt;/h3&gt;
&lt;p&gt;在確認要回到那一個版本後，便可以利用 hg update 幫助我們完成這個任務了。這個指令的功能是將工作目錄的內容回復成指定的版本，實際的操作如下：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg update -r 版本 #update 可以用 up 取代
&lt;/pre&gt;
&lt;p&gt;基本上參數「-r」都是代表版本，這裡也不例外。在這個指令中，如果不加上這個參數的話，默認會回到最新的版本(tip)。透過這個功能，我們就可以回到之前某一個時間點後再重新開始，而且同樣可以提交，只是多出一個分支，變成有多個「頭(head)」的狀態，之前分支並不會刪除，一樣可以透過 hg update 回到該版本。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/23436256-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/23436256-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;使用的時候，如果工作目錄中還有尚未提交的修改，Mercurial 就會主動嘗試解決這個問題，將兩個版本盡量完美的結合，假設新的版本中多了一個檔案「女友的內褲.py」，回到舊版本時這個檔案就會保留在工作目錄中。但整體而言，這應該不是我們所希望看到的情況，所以若還想保留當前工作目錄的版本，就先提交吧！反之若覺得不需要保留新修改的版本時，就可以加上參數「--clean」放棄修改的內容：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg update --clean -r 版本 #--clean 可以用 -C 取代
&lt;/pre&gt;
&lt;p&gt;這裡再提一個小功能，對於常穿梭在各個版本之間的人，有時可能會發生搞不清楚現在自己在那一個版本的情況，碰到這種情況，便可以使用下列指令查詢：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg parent
&lt;/pre&gt;
&lt;p&gt;這樣便會回傳使用者當前的版本資訊。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h3&gt;小結&lt;/h3&gt;
&lt;p&gt;對單獨的開發者來說，上面提到的功能就已經相當夠用了，這裡我整理出一個簡易的開發流程，這只是個參考，工作流程因為而異，愉快就好。&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;使用 hg init 開新專案，並且用 hg add 追蹤想追蹤的檔案。&lt;/li&gt;
&lt;li&gt;盡情修改已追蹤的檔案, 直到完成一個段落。&lt;/li&gt;
&lt;li&gt;測試效果，如果覺得效果不錯，那就提交內容給版本庫，反之效果不好的話，就使用 hg revert 回復到最後提交的版本或用 hg update 回到別的版本。&lt;/li&gt;
&lt;li&gt;重覆上一個步驟，直到完成為止。&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h2&gt;團隊合作開發&lt;/h2&gt;
&lt;p&gt;對於個人開發者來說，之前提到的功能就已經非常實用了，但 Mercurial 的功能並不僅於此，多人團隊開發才是它發揮真正實力的場合。&lt;/p&gt;
&lt;p&gt;多人開發的困難點有很多。舉例而言，若是多人開發，那麼每個人都必須人手一份原始碼，這樣才能得到最大的效益，但由於每個人都有權限修改，所以當我在修改一份原始碼時，我很難保證是不是別人也正在處理同一份原始碼。我也不能保證我現在正在處理的問題，是不是別人早就處理好了，只是我手上的原始碼的版本不夠新罷了。另一方面，如果後來出事了，也很難找出到底是誰幹的好事。&lt;/p&gt;
&lt;p&gt;不過話說回來，其實我們通常不是害怕兩個人同時開發了同一個功能，畢竟只要事先分配好了，這些都不是個問題，真正的問題在於「當甲開發了 A 功能，而乙開發了 B 功能，我要怎麼把這兩份原始碼輕鬆地合而為一而不會出事呢？」，這才是大問題！&lt;/p&gt;
&lt;p&gt;使用 Mercurial 的好處就在這裡，雖然它沒有神奇到可以自動完全解決合併時發生的所有問題，但它的好處是能夠提供足夠的資訊協助我們處理問題。這版本是誰寫？用 hg log 即可，與要合併的版本內容差在那裡？用 hg diff 查看。甚至在大部分的情況下，Mercurial 可以直接幫我們合併兩個版本的內容(當然使用者還是得檢查一下有沒有問題)。&lt;/p&gt;
&lt;p&gt;詳細的內容，後面會介紹。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-clone&quot;&gt;
&lt;h3&gt;hg clone&lt;/h3&gt;
&lt;p&gt;隨著時間過去，自然也有不少「有識人士」能認同小弟我的「理想」，認同開發出一個女友能讓世界變得更加美好……那麼接下來的問題就是－－要怎麼讓新來的開發者能夠輕鬆取得我開發的成果並且能在開發時與我共同享受 Mecurial 所帶來的美好呢？首先，自然要先取得我的程式碼和版本庫資訊，該怎麼做呢？這麼做即可：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg clone 來源位置(我工作目錄的位置) [目標位置]
&lt;/pre&gt;
&lt;p&gt;這樣一來，目標位置就會產生一個工作目錄，如果不填「目標位置」的話，就會在當前目錄。這個新的目錄除了程式碼外，也會有一份內容完整的版本庫(.hg)，直觀來說 hg clone 做的事其實就像是直接把來源的工作目錄複製到目標位置而已。&lt;/p&gt;
&lt;p&gt;事實上，不管使用什麼方法，對方只要能的取得一份完整工作目錄就行了。我們甚至可以選擇用手動的方式複製一份工作目錄傳給對方，反正都可以達成一樣的目的。&lt;/p&gt;
&lt;p&gt;話說回來，這個方法自然還是和手動複製有一點差別，首先是這個方法可以支援不同的傳遞媒介，比如說我們可以使用下列幾種來源：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
/home/marco79423/girlfriend                 #檔案路徑
http://marco79423.twbbs.org/girlfriend      #網址
ssh://hg&amp;#64;bucket.org/marco79423/girlfriend   #SSL
&lt;/pre&gt;
&lt;p&gt;又比如說，假設來源位置和目標位置是同一檔案系統的話，hg clone 複雜的速度還會快一點，而且所佔的空間也會少一點，理由是因為其採用一種稱為「寫時複製」的方式，如果沒有改變內容的話，會使用類似「捷徑」的方式共用同一份資料，直到修改時才會真的複製出一份。&lt;/p&gt;
&lt;p&gt;不過大體而言，我們可以想像就是單純把工作目錄複製過去，所以對方也擁有了一份完整的版本庫，換言之，就算複製過去的那一霎那，自己的電腦馬上爆炸，而且衝上了青天，方圓百里立刻被轟成了飛灰……也沒什麼關係。因為還有一份完整的「女友」在對方那兒。也就是說，使用 Mercurial 開發時，每一位開發者都代表一份完整的備份，這樣可以讓寶貝「女友」更加安全。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-pull&quot;&gt;
&lt;h3&gt;hg pull&lt;/h3&gt;
&lt;p&gt;既然每位開發者都會有一份屬於自己的版本庫，那麼版本庫之間自然也必須有方法可以互相交流，才能保證每一個版本庫的內容都是一致的。因此 Mercurial 提供了「推送」和「拉回」兩種動作給我們使用。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/234405d0-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/234405d0-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;「推送」指的是將自己的版本庫推送給別人，而「拉回」則是將別人的版本庫推回給自己。&lt;/p&gt;
&lt;p&gt;至於實際上要怎麼運作呢？雖然確實可以開發者兩兩互相交流版本庫，但這樣不太能保證交流的版本確實是最新的，所以通常我們會選擇其中一個版本庫當作標準，比如說眾人共同覺得小弟我最仁義正直，所以決定以我的版本庫為標準。那麼對於其餘的開發者而言，只要每次在開工前都先從我的版本庫「拉回」新的版本，就可以保證自己擁有的版本是目前最新的，然後才開始工作。等到終於開發完一個段落並且提交了新的版本後，再將新版本「推送」給我。這樣別人就可以透過我的版本庫取得當前最新的版本(有點繞口，對吧？)。&lt;/p&gt;
&lt;p&gt;那麼要如何拉回別人新的版本庫呢？可以這樣做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg pull [目標位置]
&lt;/pre&gt;
&lt;p&gt;「目標位置」指的是對方的版本庫，也就是從對方的版本庫拉回最新的版本給自己的版本庫。其實 pull 後面的「目標位置」可以省略，因為 Mercurial 會自動採用預設的位置，若專案是由 hg clone 取得的話，預設的位置就會是 hg clone 的來源位置。&lt;/p&gt;
&lt;p&gt;當然我們也可以自行指定預設的位置，只要在工作目錄的「.hg」裡建立一個名為「.hgrc」的文字檔，並在裡面加上：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
[path]
default = 來源位置
&lt;/pre&gt;
&lt;p&gt;就行了。Mercurial 會自動推送到這裡填的「來源位置」。&lt;/p&gt;
&lt;p&gt;看到「.hgrc」這個檔名，想必聰明的你就能猜到這和之前提到在家目錄建的「.hgrc」的其實是一樣的，先前提到的設定也可以寫進這個檔案中，差別是放在這裡的「.hgrc」的設定只限於這個專案使用。&lt;/p&gt;
&lt;p&gt;要注意的是「拉回」的只是版本庫的資訊，工作目錄的內容並沒有變化，因此「拉回」後記得使用 hg update 更新至最新的版本。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-push&quot;&gt;
&lt;h3&gt;hg push&lt;/h3&gt;
&lt;p&gt;至於推送的方法也是大致相同，可以這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg push [目標位置]
&lt;/pre&gt;
&lt;p&gt;也就是我要將我的版本庫裡最新的版本推送給對方。&lt;/p&gt;
&lt;p&gt;這裡要提一下，實際上我們實行「推送」和「拉回」動作時，對象通常不會是一個開發者的工作目錄，雖然不是不行，但卻不是個好方法，畢竟隨便更動別人家目錄的內容不是有點怪怪的嗎？&lt;/p&gt;
&lt;p&gt;所以說最常見的情況是會為專案建立一個伺服器使用，以伺服器的版本庫當作標準，實行「拉回」和「推送」。不過這對一般人而言，似乎有點壓力，所以不是本文的範疇。有一個更簡單的做法就是請別人來託管，這樣就不會有任何技術上的壓力了。有很多網站都有提供類似的服務，比如說好用的 Bitbucket 就是不錯的例子，不但專案個數和大小都沒有限制，而且還是完全免費的。詳細的做法可以參見官網，並不難使用。設定完後，就可以使用類似下面的語法「推送」和「拉回」版本庫了。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg pull http://bitbucket.org/使用者帳號/專案名稱
&lt;/pre&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg push http://bitbucket.org/使用者帳號/專案名稱
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-merge&quot;&gt;
&lt;h3&gt;hg merge&lt;/h3&gt;
&lt;p&gt;雖然剛開始開發的時候大家的版本庫都是一樣的，但隨著時間過去，開發者們會不斷地提交各自的修改給版本庫，開發者 A 提交了自己的版本，開發者 B 也提交了自己的版本，假設開發者 A 和開發者 B 在版本 1 的時候都是相同的，但隨著各自修改和提交，就會出現不同內容的版本 2、版本 3 同時並行。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/23447646-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/23447646-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;碰到了這種情況，我們就不能隨便使用「推送」的動作了，因為現在已經出現問題了，如果要「推送」的話，就必須先把問題解決。&lt;/p&gt;
&lt;p&gt;雖然不能「推送」，但我們還是可以做「拉回」的動作。不過「拉回」後就會發生上圖 8 的情況。出現了多個頭(head)的情況，由於 Mercurial 不知道那一個才是真正的頭(head)，所以我們必須處理這個問題。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;想知道現在是不是多個頭(head)的情況？可以使用 hg heads 來查看現在有那些頭(head)。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;但其實無論選擇那一個當真正的頭都不對，因為我們想要的應該是同時擁有兩個人的開發成果才對，也就是說我們必須將這兩個頭版本的成果「合併」起來，怎麼做呢？我們可以為兩個頭版本各自準備兩個資料夾，然後再分別比對每一個檔案，找我們要的片斷，修正成正確的版本，再檢查......停！打住！顯然我們不用這麼麻煩，因為我們有使用 Mercurail(得意)！&lt;/p&gt;
&lt;p&gt;這時，我們就可以使用 Mercurial 的合併功能了，語法如下：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg merge [目標版本] #此例其實不需要填寫「目標版本」，因為已經出現多個頭的問題了，Mercurial 自然知道要優先合併那一個版本。
&lt;/pre&gt;
&lt;p&gt;這樣一來 Mercurial 就會先幫助我們完成初步合併的動作，畢竟 Mercurial 紀錄的是每一個變動，所以版本之間的合併，只要不發生衝突，並不是沒辦法推算出來。比如說你新增了「女友的肚子.py」，而我修改了「女友的鼻子.py」這合併起來自然不會有問題。&lt;/p&gt;
&lt;p&gt;雖然大部分的情況下，Mercurial 都會自動幫助我們解決，但也不是每次都能這麼輕鬆，還是有一些情況必須我們自己解決。Mercurial 這時會很聰明的試著呼叫所有可以處理的工具幫助我們完成合併。&lt;/p&gt;
&lt;p&gt;合併後的成果會放在工作目錄裡，我們可以仔細檢查工作目錄的內容有沒有問題，也許某些細節還需要再調整，等確定沒有問題後，我們還要再將成果提交才算真正完成合併的工作。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/23432840-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/23432840-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;你可能有注意到每一次提交後，版本號都會加一，由於每個開發者都會提交自己的版本，所以當出現分支時，就會發生雖然是同一個版本號但是實際上代表不同版本的情況。這也是為什麼之前說不要使用版本號和別的開發者溝通的原因。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hg-branch&quot;&gt;
&lt;h3&gt;hg branch&lt;/h3&gt;
&lt;p&gt;看到這裡，想必應該有人會有一個疑問－－為什麼要讓所有人都有自己的版本庫呢？只有一個的話不就不需要考慮版本庫分支或合併的問題了嗎？&lt;/p&gt;
&lt;p&gt;這是自然是有原因的，除了之前提到可以有更好的備份效果外，效能也會有所提升，因為如果每個人都要直接和同一個版本庫溝通的話，這個版本庫的運行就會很吃力，但若使用分散式的方式，多少可以就可以幫忙分擔一些流量了。反正每一個人的版本庫都是完整的，所以我們不見得一定要從誰的版本庫取得最新的版本。&lt;/p&gt;
&lt;p&gt;另一個很重要的原因是假設我現在要開發一個功能，而這個功能有點複雜，一次我只能寫完一個部分，那麼當我寫完成一段落時，到底該不該提交給版本庫呢？若一旦提交了，別人可能就會受到那份不完善的程式碼影響(結下仇恨的因子)。&lt;/p&gt;
&lt;p&gt;但如果我真的寫完後才提交，那就失去了使用版本控制的意義了，只要中途出現了差錯，就會沒辦法回復，只能砍掉重寫。&lt;/p&gt;
&lt;p&gt;如果每一個人都有自己的版本庫的話，就不會有這樣的問題了。我一樣可以寫完一個片段就提交，反正只會提交到自己的版本庫，並不會影響到別人。等到程式碼穩定以後再和別人的版本庫交流就行了，相對比較不會出問題。&lt;/p&gt;
&lt;p&gt;話說回來，這樣似乎還是有一個問題－－只要程式碼還沒穩定，我就永遠不能別人的版本庫同步了。不然一樣還是會影響到別人，強迫別人「享受」你未完成的程式碼的結果就是累積了新的仇恨值。但如果一直都不同步的話，又有誰知道你有沒有在工作呢？&lt;/p&gt;
&lt;p&gt;其實有一個更好的方法，那就是使用「分支(branch)」的功能，每寫一個功能，就多開一個分支，要開發「女友的肚臍.py」就開一個肚臍分支，要開發「女友的性別.py」就新開一個性別分支。每個人各自開一個新分支，等到新功能開發完成後，再和主幹合併即可。感覺就像是之前提到的多個頭的情況，差別是這次是我們主動做分支而已，要合併時一樣可以用 hg merge 合併。&lt;/p&gt;
&lt;p&gt;這樣一來，就算別人沒有開發完成，同步的時候也只會看到一個未完成的分支而已，主幹(預設的分支)還是開發完成而且穩定的程式碼。而且就算開發失敗了也無關緊要，只要放棄這個分支即可。另一方面，我們還可以透過這些分支掌握別人的開發進度。&lt;/p&gt;
&lt;p&gt;至於開新分支的方式如下：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg branch 分支名稱
&lt;/pre&gt;
&lt;p&gt;下完這個指令後，之後所有的提交都會改在這個分支上進行，並不會影響主幹的內容，除了主幹可以開分支外，分支也可以繼續開分支，無限分支下去。如果想切換不同的分支時，可以使用：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg update 目標分支名稱 #update 一樣可以用 up 取代
&lt;/pre&gt;
&lt;p&gt;切換不同的分支，其中「default」代表主幹，也就是預設的分支，如果沒有開啟別的分支的話，預設的提交都是在這個分支上進行。
如果 hg branch 後面不加上目標分支的話，就會回傳目前所在的分支為何。&lt;/p&gt;
&lt;p&gt;當分支的內容開發完成，我們可以要求 Mercurial 將分支的內容合併回主幹。由於 hg merge 做的事是將目標版本的內容合併入當前所在的版本中，因此我們必須先回到主幹後才能進行合併：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg up default
&lt;/pre&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg merge 分支名稱
&lt;/pre&gt;
&lt;p&gt;合併的過程與之前提到的做法相同，碰到衝突必須解決，然後提交後才算完成合併。&lt;/p&gt;
&lt;p&gt;如果分支的工作完全搞定後，我們可以提交最後一次，並透過參數「--close-branch」關閉這個分支。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg commit -m &amp;quot;分支開發完成&amp;quot; --close-branch  #commit 可以用 ci 取代
&lt;/pre&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;如果有使用分支的話，建議每次提交時註解都要加上所在的分支名稱，以方便查看。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;此外，我們可以使用：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; hg branches
&lt;/pre&gt;
&lt;p&gt;查看目前有那些分支正在進行，已經關閉的分支不會列在上面。但要注意的是就算已經關閉了，也不能再開一個同名的分支(不用擔心不小心犯錯，因為用了同樣的名稱 Mercurial 會慘叫給你看)。&lt;/p&gt;
&lt;p&gt;分支的應用相當廣泛，舉例來說，我們可以區分「default」和「dev」兩個主要分支，「default」代表發行版，「dev」則代表開發版。「dev」分支代表軟體的最新進展，永遠不會關閉，會不斷地開發新的功能(可以從這個分支再分支出去)，等到覺得可以了以後，再合併至「default」，然後「dev」則繼續開發新功能。&lt;/p&gt;
&lt;p&gt;之所以這麼做的原因是假設發行的版本出現 bug 了，但我還想要繼續開發新功能，而團隊這麼多人，自然會需要有人開發新功能，有人處理 bug，總不能把 bug 全部處理完以後才開發新功能，這是耗費人力的做法。&lt;/p&gt;
&lt;p&gt;而採用這種模式的話，某些人可以在「default」分支負責努力解決 1.0 的 bug，而負責「dev」分支的人，則繼續開發 2.0 的新功能，等到 1.0 的 bug 處理完以後，便可直接發佈 1.1。&lt;/p&gt;
&lt;p&gt;可是開發中的 2.0 一樣可能會隱含 1.0 的 bug 呀？難道也要重新修改一遍嗎？不需要！
因為我們可以反過來將「default」合併至「dev」分支，這樣「dev」隱含的 bug 也就一併被處理了。&lt;/p&gt;
&lt;p&gt;戲法人人會變，巧妙各有不同，其實怎麼使用都可以，舉例來說「女友」的開發者們可能不都像本作者一樣正直仁義善良，所以「個別的」開發者也可以另外開一個只共用部分程式碼的特殊分支，像是「十八禁」分支之類的。這樣用當然也行，要知道工具就是拿來用的，使用愉快就好了，不要反而被工具束縛。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;實際使用的時候，你可能會發現 Mercurial 會推薦使用 hg bookmark，這是新版本推薦使用的功能，不過這裡有一些爭議，有些人不喜歡這個功能，還是比較推薦使用 hg branch(比如說我，守舊派一隻)，你可以在網路上找到相關的討論。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-3&quot;&gt;
&lt;h3&gt;總結&lt;/h3&gt;
&lt;p&gt;最後再整理一下新版本的工作流程(一樣是因人而異，你爽就好)：&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;使用 hg init 開新專案或使用 hg clone 複製別人的專案&lt;/li&gt;
&lt;li&gt;如果有一段時間沒工作的話，先使用 hg pull 取得最新的版本內容，再用 hg update 更新到最新版本&lt;/li&gt;
&lt;li&gt;盡情修改已追蹤的檔案, 直到完成一個段落&lt;/li&gt;
&lt;li&gt;測試效果，如果覺得效果不錯，那就提交內容給版本庫，反之效果不好的話，就使用 hg revert 回復到最後提交的版本或用 hg update 回到別的版本。&lt;/li&gt;
&lt;li&gt;當成果穩定後，使推送至伺服器。如果發現自己不是最新的版本，便用 hg pull 取得最新的版本，再用 hg merge 合併，直到確定沒問題後，提交合併的版本，再進行推送。&lt;/li&gt;
&lt;li&gt;重覆上一個步驟，直到完成為止。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;至於只是想惡搞，想實驗新功能，可以直接使用 hg clone 複製一份，然後再上面修改。反正只要沒有推送，就不會影響到別人，而反過來說，萬一寫得太好了，也可以選擇推送，一切取決於自己。&lt;/p&gt;
&lt;p&gt;最後做個總結，雖然沒有把所有的功能講完，有很多好用的功能還沒有提到，不過了解目前的這些功能，應該就可以很好的工作了，至於比較進階的功能等以後有機會寫「再探 Mercurial」的時候再說吧。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[累死雞記帳]]></title>
        <id>https://blog.marco79423.net/articles/%E7%B4%AF%E6%AD%BB%E9%9B%9E%E8%A8%98%E5%B8%B3/</id>
        <link href="https://blog.marco79423.net/articles/%E7%B4%AF%E6%AD%BB%E9%9B%9E%E8%A8%98%E5%B8%B3/"/>
        <updated>2013-09-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[






名稱
累死雞記帳

使用語言
Qt, Java

使用平台
Android, Windows

版本資訊
0.1

作者
兩大類

原始碼
GitHub ...]]></summary>
        <content type="html"><![CDATA[&lt;img alt=&quot;1.png&quot; src=&quot;https://blog.marco79423.net/backend/static/24792610-2cd0-11f1-bbd2-32abd871d697/&quot; style=&quot;width: 200px;&quot; /&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;13%&quot; /&gt;
&lt;col width=&quot;87%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;名稱&lt;/td&gt;
&lt;td&gt;累死雞記帳&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用語言&lt;/td&gt;
&lt;td&gt;Qt, Java&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用平台&lt;/td&gt;
&lt;td&gt;Android, Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;版本資訊&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;作者&lt;/td&gt;
&lt;td&gt;兩大類&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;原始碼&lt;/td&gt;
&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://github.com/marco79423/accountbook&quot;&gt;GitHub&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;下載連結&lt;/td&gt;
&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://blog.marco79423.net/backend/static/247d1acc-2cd0-11f1-bbd2-32abd871d697/&quot;&gt;Android 端載點&lt;/a&gt;, &lt;a class=&quot;reference external&quot; href=&quot;https://blog.marco79423.net/backend/static/2479771e-2cd0-11f1-bbd2-32abd871d697/&quot;&gt;Windows 端載點&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;這個作品的目標是希望能透過手機端和 PC 端的結合，為學生提供一個有手機的便攜性和 PC 的功能性而且開源、簡單易用的記帳軟體。
為了這個目的，我觀察了許多免費的記帳軟體，發現這類的軟體不是太過陽春，便是太過複雜而難以使用。因此專案的目標是希望能迎合學生的習性，
比如說不能使用太多的專有名詞，因為學生沒有足夠的知識；記帳的時候不能太麻煩，因為學生很懶；而且要能備份，因為學生的手機很容易掉等。&lt;/p&gt;
&lt;p&gt;為了簡化操作，不讓程式太複雜，也不希望因為簡化而讓程式太過陽春，因此累死雞記帳只保留真正重要的功能－－紀錄支出。
我認為對學生來說最重要的工作是學習，學習如何正確地花錢，而不是把學習的時間都用來賺錢！我的目標是要能幫助學生了解自己平常是花錢的，
透過用錢的分析，進而了解自己的生活方式有何待改進之處。&lt;/p&gt;
&lt;p&gt;累死雞記帳的每一項支出都以照片為主，使用者可以先只拍照，事後才補充支出的詳細內容。另外，本軟體可以透過 Dropbox 同步記帳資料庫，
並由 PC 端的軟體管理。&lt;/p&gt;
&lt;div class=&quot;youtube&quot; align=&quot;left&quot;&gt;&lt;iframe width=&quot;600&quot; height=&quot;400&quot; src=&quot;https://www.youtube.com/embed/HXCBhGiq2iQ&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;p&gt;使用方式介紹(請不要太在意我凌亂的桌子)&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;這個作品是我的 Android 處女作，由於我只有一台手機，所以我不確定別人可不可以用，如果有什麼問題或意見，
也可以留言詢問。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[UNIX 常用指令 - find]]></title>
        <id>https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-find/</id>
        <link href="https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-find/"/>
        <updated>2013-07-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。
在 FreeBSD 中，如果我們想要找尋某一個檔案的話，就可以用「find」這個指令快速找到想要的檔案，
比如說現在要找尋一個檔名為「chicken ...]]></summary>
        <content type="html"><![CDATA[&lt;p class=&quot;rubric&quot;&gt;這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。&lt;/p&gt;
&lt;p&gt;在 FreeBSD 中，如果我們想要找尋某一個檔案的話，就可以用「find」這個指令快速找到想要的檔案，
比如說現在要找尋一個檔名為「chicken.py」的檔案，但不知道在那兒，那麼我們就可以利用這個指令搜尋。
當然了，如果 find 只有這點實力的話，那就可以很輕鬆的用別的指令來取代它，並不值得一提了。&lt;/p&gt;
&lt;p&gt;主要的問題在於通常我們會找不到檔案，常常是因為我們忘記檔案確切的名稱，甚至是本來就不知道名字，連檔案是否確實存在都不敢肯定，
碰到這種情況，用常規的辦法就比較難找了。&lt;/p&gt;
&lt;p&gt;舉一個例子，假設現在我想知道 chicken(某位使用者)有沒有藏「秘密日記」在檔案系統中，但因為我根本就不知道這個檔案確實的名稱，
甚至連存不存在都不知道，所以只能用一些蛛絲馬跡來猜測，這時使用 find 指令就是非常好的辦法。&lt;/p&gt;
&lt;p&gt;那麼如何使用 find 指令呢？大致的格式如下：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find 要想找尋的位置 條件
&lt;/pre&gt;
&lt;p&gt;使用的方法相當的直覺，主要有兩個部分，首先是「想要找的位置」，顧名思義，即是指檔案可能存在的位置，
好比說我覺得 chicken 的「秘密日記」比較可能放在她的家目錄，那我就可以指定 chicken 的家目錄當成我要找尋的位置，
然後 find 就會查找這個目錄裡面所有的檔案和目錄；至於「條件」則是指篩選的條件，chicken 的家目錄這麼多檔案，
我可以利用一些「條件」篩去不要的檔案，
比如說此例我就可以設一個條件指定日記可能的檔名，把不符合檔名的檔案篩掉。&lt;/p&gt;
&lt;p&gt;首先，由於日記的英文是「diary」，所以想要找 chicken 的日記的話，可以先試試看「diary」這個檔名，所以我下這樣的指令：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken -name diary
&lt;/pre&gt;
&lt;p&gt;「-name」即是檔案名稱的意思，這行指令的意思是我想找檔名為「diary」的檔案，如果找到了，就列出來，
如果沒找到，就什麼都不列。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken -name diary
/home/chicken/.private/diary  #成功的假想
&lt;/pre&gt;
&lt;p&gt;不過想當然爾，女生的日記不是這麼容易找尋的，所以並沒有找到。
主要問題在於檔名剛好就是「diary」的可能性太低，因為她可能會取「chicken-diary」、「mydiary」之類的名稱，
那麼用這種方法就找不到了。&lt;/p&gt;
&lt;p&gt;不過話說回來，反正不管什麼組合，至少我推測她應該會使用「diary」這個關鍵字，所以我可以這樣下：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken -name &#39;*diary*&#39;
&lt;/pre&gt;
&lt;p&gt;沒錯，find 可以支援使用像是「?」、「*」這類的符號做不精確的搜尋，「*」代表這個位置可能會有零個、一個或是多個字元，
具體的是什麼我不知道，因此「*diary*」的意思就是在「diary」前後都可能有未知的字串，像是「diary」、「chicken-diary」、
「mydiary」和「diary-chicken」都算符合條件。至於之所以外面要用引號包起來是為了怕 Shell 誤判「*」的意思，
如果不想用的話，也可以在「*」前面加上反斜線「\」代替。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;「嗯？似乎還是找不到？」&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;「對喔~她也有可能用中文名稱呀？像是「日記」、「我的日記」之類的？」&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;沒錯，她使用的關鍵字，除了「diary」外，也有可能是「日記」呀！也就是說我的條件並不只一個！
那怎麼辦呢？沒關係！事實上 find 也支援複雜的條件，可以使用「AND」、「OR」和「NOT」三種邏輯，邏輯「AND」用「-a」表示、
邏輯「OR」則用「-o」表示，而邏輯「NOT」則是「!」，因此我可以這麼下：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken -name &#39;*diary*&#39; -o -name &#39;*日記*&#39;
&lt;/pre&gt;
&lt;p&gt;這個指令的意思是找出檔名中包含「diary」或「日記」關鍵字的檔案。&lt;/p&gt;
&lt;p&gt;話說回來，如果女生的日記這麼好找的話，女生就不是女生了，所以果然找不到！
問題在那裡呢？對呀！誰說日記就只能放在家目錄呢？所以應該這麼下：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find / -name &#39;*diary*&#39; -o -name &#39;*日記*&#39;
&lt;/pre&gt;
&lt;p&gt;於是就出現了：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
/.private/marco-diary
&lt;/pre&gt;
&lt;p&gt;咦！如果我這麼下的話，不是整個系統的使用者的日記都有可能被我找出來嗎？
所以似乎應該先鎖定一下檔案的所有者才對！幸好 find 也可以輕鬆的做到這一點，我們可以用「-user」代表限定使用者的名稱、
用「-group」代表限定群組，而比較進階的，也可以使用「-uid」、「-gid」限定使用者的 UID 和 GID。
不過在這裡我應該可以假定，這個日記的所有者應該是「chicken」才是，因此我可以這樣下：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find . \( -name &#39;*diary*&#39; -o -name &#39;*日記*&#39; \) -a -user chicken
&lt;/pre&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;-a 可以省略，因為邏輯「AND」是預設值。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;這裡可以注意到，我有加上括號代表裡面的內容先判斷，表示先判斷檔名是否包含「diary」或「日記」，
符合條件後，再判斷所有者是不是「chicken」。另外要注意的是前面要加上反斜線「\」，這一樣是為了避免 Shell 誤判。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;刪除使用者時，使用「-user」很方便。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;於是就出現了：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find . \( -name &#39;*diary*&#39; -o -name &#39;*日記*&#39; \) -a -user chicken
/home/chicken/.abc/nbrjnoql.default/data/linux64/components/TCPSocketParentIntermediary.js
/home/chicken/cssulabug-diary/ngsagfafiex.cfaf
/tmp/Radfareadfdrsdiaryafafd
....(數千行
&lt;/pre&gt;
&lt;p&gt;不行！還是有一堆無關緊要的檔案！我想是因為「diary」這個詞太常見了，所以果然還是必須多加一些條件才行.....對了！
她平常都是晚上使用電腦的，所以如果她有寫日記的話，多半也是晚上寫才是，如果我多加一個條件，限制檔案修改的時間為晚上的話，
是不是就可以去掉像是 log 之類的檔案呢？好加在 find 太強大了，也可以做到這一點。
比如說我們可以設定「找出所有修改時間為十分鐘前的檔案」或是「找出一天內被存取過的檔案」這種條件。
具體的做法「-atime」代表存取的時間，而「-mtime」則代表修改的時間，時間單位為天，如果不想以天為單位的話，
也可以改用以分鐘為單位的另外兩個參數「-amin」和「-amin」。&lt;/p&gt;
&lt;p&gt;比如說剛剛 chicken 在半個小時前，有用過十分鐘電腦，那我可以這樣下指令，找出她這段時間有存取的檔案：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken -amin +20 -amin -30
&lt;/pre&gt;
&lt;p&gt;加號「+」代表超過，而減號「-」則表示少於，此例的意思是找出所有「/home/chicken」最近二十分鐘到三十分鐘之間有被存取的檔案。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;這裡要注意的是「+1」不代表一分鐘，而是超過一分鐘，也就是二分鐘以上。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;加上時間條件，想必就可以找到 chicken 的日記了......咦！？&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken -amin +20 -amin -30
/home/chicken/diary/0
/home/chicken/diary/1
/home/chicken/diary/2
/home/chicken/diary/3
/home/chicken/diary/4
...
/home/chicken/diary/diary/1
/home/chicken/diary/diary/2
...(數千行
&lt;/pre&gt;
&lt;p&gt;竟然是傳說中的烏賊戰術！利用大量的無用檔案掩蓋真正的有用的資料，以避免像是本作者這種混蛋偷看她的日記！
這果然是個好辦法，不過這還是難不倒我，可以注意到她其實是故意將某一目錄取名為「diary」，
然後在這個目錄中建立大量無用的檔案掩蓋。
雖然是好方法，但我還是可以利用 find 的「-type」參數限制檔案的類型，最常用的類型有兩個，分別是一般檔案「f」和資料夾「d」，
因此這裡我可以這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken \( -name &#39;*diary*&#39; -o -name &#39;*日記*&#39; \) -a -type f
&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(這裡為了避免太複雜，所以我省略了時間的條件)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;這樣代表符合的檔案類型必須是一般檔案，而不是目錄。&lt;/p&gt;
&lt;p&gt;於是......&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken \( -name &#39;*diary*&#39; -o -name &#39;*日記*&#39; \) -a -type f
/home/chicken/haha/diary1
/home/chicken/haha/diary2
/home/chicken/haha/diary3
/home/chicken/haha/diary4
...
/home/chicken/haha/stupid-marco/diary1
/home/chicken/haha/stupid-marco/diary2
/home/chicken/haha/stupid-marco/diary3
...(還是數千行
&lt;/pre&gt;
&lt;p&gt;這.....好！沒關係，我還有一招！反正這些「烏賊檔」多半是用指令「touch」生的，所以檔案大小都是 0KB，但如果是真的日記的話，檔案大小自然就不可能是 0KB！
所以我可以使用「-size」來解決這個問題，「-size」可以指定限制的檔案大小，而且可以支援多種單位，比如說「c」代表「bytes」，而「k」代表「KB」。&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;38%&quot; /&gt;
&lt;col width=&quot;63%&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead valign=&quot;bottom&quot;&gt;
&lt;tr&gt;&lt;th class=&quot;head&quot;&gt;符號&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;大小單位&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;c&lt;/td&gt;
&lt;td&gt;bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;k&lt;/td&gt;
&lt;td&gt;KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;在這個例子我可以這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken \( -name &#39;*diary*&#39; -o -name &#39;*日記*&#39; \) -a -type f -a -size +1k
&lt;/pre&gt;
&lt;p&gt;這行指令的意思是符合的檔案必須大於 1 KB，小於等於的不算，另外也可以看出來這裡也可以使用和限制時間相同方式使用「+」或「-」。
透過這個手段，想必就可以找出我要的日記了！&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken \( -name &#39;*diary*&#39; -o -name &#39;*日記*&#39; \) -a -type f -a -size +1k
/home/chicken/stupid/stupid/diary-big
/home/chicken/stupid/stupid/diary-big2
/home/chicken/stupid/stupid/diary-big3
/home/chicken/stupid/stupid/diary-big5
...(還是數千行
&lt;/pre&gt;
&lt;p&gt;天啊！這女人實在是太可怕了！竟然這樣也有防備！太可惡了！受不了！怎麼這麼難找呢？&lt;/p&gt;
&lt;p&gt;......好吧！&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; sudo touch /home/chicken/.diary #幫她生一個日記
&lt;/pre&gt;
&lt;p&gt;於是如果以後我想要找到 chicken 的日記，我就可以看到：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken -name &#39;.diary&#39;
/home/chicken/.diary
&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;「耶！終於找到了！(有種莫名感傷的意味)」&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;「不過話說回來，找到她的秘密日記後，又該如何做呢？」&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;那還用說？身為一個專業的壞人，找到以後自然應該這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; find /home/chicken -name &#39;.diary&#39; -exec ln &#39;{}&#39; &#39;/home/marco/chicken-diary&#39; \;
&lt;/pre&gt;
&lt;p&gt;find 除了能夠找尋我要的檔案外，也可以在找到檔案後，對每個檔案執行某一個指令，方法是使用參數「-exec」或「-ok」，
兩個參數的差別只在「-ok」每對一個檔案執行一次指令，都會詢問一次，比如說刪除的時候，就可以使用「-ok」，
避免誤刪。&lt;/p&gt;
&lt;p&gt;「{}」代表抓到的檔名，習慣上還會外面加上引號，以免出現檔名包含空白之類的問題。
而指令後面的分號「;」則是使用「-exec」的要求，後面一定要加上這個，才代表指令結束。
所以上例的意思就是「找到 chicken 的日記後，建一個 link 到 marco 的家目錄，連結檔的檔名為「chicken-diary」。
這樣一來，我就可以時不時的偷看到她寫的日記，然後檢查平常有沒有說我的壞話了。&lt;/p&gt;
&lt;p&gt;看到這裡，除了找到了 chicken 的日記外，相信大家也對好用的指令「find」有一定的了解，希望能對讀者有所幫助。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;「笨蛋！chicken 又不寫日記，最好是找得到啦~」&lt;/em&gt;&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[為什麼 Qt 要有 Signals/Slots ？]]></title>
        <id>https://blog.marco79423.net/articles/%E7%82%BA%E4%BB%80%E9%BA%BC-qt-%E8%A6%81%E6%9C%89-signalsslots/</id>
        <link href="https://blog.marco79423.net/articles/%E7%82%BA%E4%BB%80%E9%BA%BC-qt-%E8%A6%81%E6%9C%89-signalsslots/"/>
        <updated>2013-07-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Signals/Slots 可說是 Qt 程式設計的基石，也是它最大的特色。尤其是在 GUI 程式設計時，
對比其他使用回呼函式的設計手段，就可以體會其巨大的好處，而 Qt 強大的 GUI ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;Signals/Slots 可說是 Qt 程式設計的基石，也是它最大的特色。尤其是在 GUI 程式設計時，
對比其他使用回呼函式的設計手段，就可以體會其巨大的好處，而 Qt 強大的 GUI 函式庫，也是根據這個機制打造，
可說是 Qt 的根本。&lt;/p&gt;
&lt;p&gt;說了這麼多，到底 Signals/Slots 是什麼呢？這就要從一個故事說起了......。&lt;/p&gt;
&lt;p&gt;很久很久以前，有一隻小雞名叫「顯顯」，而有另一隻小雞名叫「處處」，顯顯負責在螢幕上顯示一個上面寫著「發射」的按鈕，
而處處則負責將核彈發射至指定地點。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2229768a-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2229768a-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;一旦有人按下顯顯負責的按鈕，顯顯就會將這個訊息告訴處處。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2229b8ca-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2229b8ca-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;而處處接到這個訊號時，就會開始處理發射核彈的工作。&lt;/p&gt;
&lt;p&gt;欣賞立法院火紅末日之餘，我們來研究一下這個「核彈發射程式」的運作。可以看出來，
這裡我們想要把程式區分成「顯示單元」和「運算單元」，「顯示單元」就是顯顯，
而「運算單元」則是處處。&lt;/p&gt;
&lt;p&gt;之所以要區分「顯示單元」和「運算單元」是因為這兩者都是屬於常常變動，而且又是互相獨立的關係。
比如說那一天顯顯被一隻蟲吃掉了，可以很輕易的用「顯顯二號」代換，反正對處處而言，
顯示的方式並不重要，是紅色的按鈕還是綠色的按鈕並沒有差別；套用在顯顯身上也一樣，
對顯顯來說，只要能發射核彈，是不是處處做的根本無關緊要，反正該死的立法院一樣有火紅末日......。&lt;/p&gt;
&lt;p&gt;以物件導向的想法來說，設計顯顯的時候，我們可能會覺得「按鈕」這種元件很有重用的價值，很多地方都會用到，
而且功能都是「按下去後，會做一件事」，因此我們可以設計一個通用的按鈕，這樣就不用為每一個按鈕都重新設計一個類別：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AbstractProcessor&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/*&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt; * 我是很通用的小雞顯顯&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt; */&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Button&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setProcessor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AbstractProcessor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_processor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clicked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_processor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AbstractProcessor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;之後我們再設計處處的時候，就可以像這樣設計：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/*&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt; * 小雞處處&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt; */&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Processor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AbstractProcessor&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//處理核彈發射&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;之所以要設計 AbstractProcessor 這個類別是因為我不想要讓顯顯和處處綁死在一起，
我可以輕鬆的把處處代換成做麵包的處處二號，只要處處二號也是繼承自 AbstractProcessor 即可。
完全不用改顯顯這個按鈕的程式碼。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;button1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setProcessor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;button2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setProcessor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Processor2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;因此顯顯相當通用，符合物件導向的期望。不過這個做法有一個小小的問題，
那就是由於實際上我們並不會使用到 AbstractProcessor 這個類別，所以換言之就是我多了這個類別，
多了類別就表示多了程式碼。況且這只是一個小小的例子，在實際應用時，可能因此而產生非常非常多類別，
也就是多了很多很多程式碼。&lt;/p&gt;
&lt;p&gt;這其中的關鍵點在於我們不希望讓顯顯知道處處的存在，因為這樣就會減少了設計彈性，所以才用 AbstractProcessor 取代處處，
只要處處符合 AbstractProcessor 定義的格式(此例為 process 函式)即可。而 Signals/Slots 這種機制可以漂亮的解決這個問題，
並不需要多設計 AbstractProcessor 這個類別就可以達成相同的目的。&lt;/p&gt;
&lt;p&gt;回憶一下，顯顯和處處分別負責什麼事？，顯顯負責「當有人按下按鈕，就會將這個訊息告訴處處」，而處處則是「當收到訊息後，處理核彈發射的工作」。
換個角度想就是顯顯會「發送訊號」，而處處會「接收訊號」。&lt;/p&gt;
&lt;p&gt;因此在開發 Qt 的應用程式時，我們會這樣設計：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/*&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt; * 小雞顯顯之 Qt 進化版&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt; */&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QObject&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Q_OBJECT&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;onClicked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clicked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//當按鈕按下時，會發送 clicked 訊號&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;signals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//定義「訊號」&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clicked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以注意到上面程式碼中有一個特異之處，那就是 signals 這個標籤。這是 Qt 特別定義的標籤，
表示後面定義的函式都代表「訊號(Signal)」，這種函式不用實作，定義函式名稱和所需參數即可。
而這些訊號可以透過 emit 這個關鍵字發射。上面程式碼定義的 Button 類別只做一件事，當按鈕按下時，便發送 clicked 訊號。&lt;/p&gt;
&lt;p&gt;至於處處則可以這樣設計：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/*&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt; * 小雞處處之 Qt 進化版&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt; */&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Processor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QObject&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Q_OBJECT&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slots&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//處理核彈發射&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;和原來的設計方式幾乎一樣，差別在 public slots 這個標籤，這個標籤同樣也是 Qt 定義的，
代表可以接受「訊號」的函式(Slot)。&lt;/p&gt;
&lt;p&gt;最後我就可以利用 Qt 定義的 QObject::connect 函式將兩者相連接：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//QObject::connect(顯顯, SIGNAL(有人按下按鈕！), 處處, SLOT(處理核彈發射));&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;QObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SIGNAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clicked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SLOT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()));&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;先不管這個語法是如何做到的，這個函式的意思是將顯顯(button)的 clicked 訊號與處處(processor)的 process 函式相連，
接下來只要顯顯的 clicked 訊號被發射，那就執行處處的 process 函式。&lt;/p&gt;
&lt;p&gt;可以看到幾個明顯的好處，首先是我不需要讓顯顯知道處處的存在，
只要 processor 定義的 public slots 裡函式符合 button 的 clicked 所要求的格式便可相連，
也不用定義一個額外的類別 AbstractProcessor。&lt;/p&gt;
&lt;p&gt;就好像在顯顯和處處之間建立一個通道，兩隻雞不用互相知道對方，一個只要記得往那個通道丟訊息，
另一個只要記得從那個通道接收訊息就行了。而顯顯也不用像原來那樣用一個成員變數來紀錄具體的 AbstractProcessor 為何，
也可減少很多程式碼。&lt;/p&gt;
&lt;p&gt;而且不只可以丟訊息，也可以丟資料，比如說定義的「訊號」包含兩個參數：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Sender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QObject&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Q_OBJECT&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;signals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mySignal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Receiver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QObject&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slots&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;onMySignal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;只要傳送和接收的參數符合，就可以參遞參數的資料，兩者連接的語法和原來的相似，只是多了參數的部分而已：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SIGNAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mySignal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;receiver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SLOT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;onMySignal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)));&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;發射訊號時也和原來的相似，也是多了參數的部分：&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mySignal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最重要的是這種方式會做型別檢查，如果「訊號」的參數是字串型態，那麼接收「訊號」的函式就不能是數字型態，
因此可以減少很多錯誤的可能性。&lt;/p&gt;
&lt;p&gt;另一方面，還有諸多好處像是我可以在不用撰寫修改程式碼的情況下讓「訊號」和不只一個接收「訊號」的函式相連接，
也可以讓多個「訊號」與同一個接收「訊號」的函式相連接。&lt;/p&gt;
&lt;p&gt;Qt 的 Signals/Slots 機制可以有效的減少程式碼、增加可讀性，是一個強大好用的功能，
也是 Qt 運作的基礎。而這個概念雖然是從 Qt 發展，但事實上也不只是 Qt 使用這個機制，
好比 C++ 著名的函式庫 Boost 也有提供類似的功能。所以這種機制確實有其獨到之處，值得參考。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;「媽媽說，學 Signals/Slots 機制的小孩不會變壞！」&lt;/em&gt;&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Bloomier Filter]]></title>
        <id>https://blog.marco79423.net/articles/bloomier-filter/</id>
        <link href="https://blog.marco79423.net/articles/bloomier-filter/"/>
        <updated>2013-05-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[說到 Bloomier Filter，看倌一定會覺得看起來和 Bloom Filter 很像(詳情請看 Bloom Filter )。
是的，而且兩者演算法本身確實也有相似之處，不過用途並不相同。
Bloom Filter ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;說到 Bloomier Filter，看倌一定會覺得看起來和 Bloom Filter 很像(詳情請看 &lt;a class=&quot;reference external&quot; href=&quot;/articles/bloom-filter/&quot;&gt;Bloom Filter&lt;/a&gt; )。
是的，而且兩者演算法本身確實也有相似之處，不過用途並不相同。&lt;/p&gt;
&lt;p&gt;Bloom Filter 可以紀錄「某一個元素是否存在」這種型態的資料，
我們可以利用這種資料結構來查詢某元素是否存在的資訊，如查詢「某小雞是不是在這一間雞舍？」這種訊息。
雖然 Bloom Filter 的結果不準確，可能發生「明明不存在，卻回報存在」的狀況，但因為所佔空間小，
而且搜尋速度快的特性，所以常用來做前期的篩選。&lt;/p&gt;
&lt;p&gt;但 Bloomier Filter 不同，它不會放棄資料的正確性，而且不像 Bloom Filter 只能存「某一個元素是否存在」的資訊，
因此可以當作實際存放資料的地方，是故 Bloomier Filter 雖然稱作 Filter，但並不只能做 Filter 的事。&lt;/p&gt;
&lt;img alt=&quot;weight-table.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2646fdf0-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;這類型的演算法使用的情境約略來說，就是會有很多個 Key，每個 Key 都會對應一筆資料，
而我們的目標是希望能有一種方法能夠快速的根據 Key 找到其對應的資料。
比如說「班上同學體重紀錄表」紀錄著全班同學的體重資料，Key 就是班上同學的名字，
對應的資料就是該同學的體重，而我的目標就是希望能找到一個方法，
能夠很簡單快速的用同學的名字(Key) 取得他的體重資訊(資料)。&lt;/p&gt;
&lt;img alt=&quot;hash.png&quot; src=&quot;https://blog.marco79423.net/backend/static/264730f4-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;最簡單的做法，就是用 Hash 函式(如：CRC) 對 Key 做編碼，
然後再利用編碼後的結果當成 Hash Table(存放資料的資料結構)的索引值，而索引值的對應的位置就可以放置資料內容。
從此以後，只要我知道 Hash 函式是什麼，就可以利用 Key 輕鬆取得我要的資料。&lt;/p&gt;
&lt;p&gt;由於 Hash 函式的好處在於計算速度很快，而且不受資料個數的影響(如果是用串列之類的方式找，
資料個數越多找得越慢)，所以這種找資料的方式在碰到這種情境時相當常用。&lt;/p&gt;
&lt;p&gt;不過這種做法最大的麻煩在於必須考慮「碰撞」的問題，
「碰撞」是指不同的 Key 卻編碼出相同的結果，造成搶位置的情況。
舉例來說，假設「同學甲」和「同學乙」這兩位同學編碼出相同的結果，那麼兩個人的體重資料就會對應在 Hash Table 一樣的位置，
但問題是資料只能放一份，所以就會發生不知道要放「同學甲」的體重還是「同學乙」的體重進去的情形。&lt;/p&gt;
&lt;img alt=&quot;collision.png&quot; src=&quot;https://blog.marco79423.net/backend/static/26469856-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;最常見也是最簡單的解決辦法就是不在 Hash Table 裡面存放實際的資料(體重)，
而是放一個指標來指向要存放的資料，以串列的方式來紀錄資料，
這樣我們就不用擔心放不下或是浪費空間的問題了。不過這樣一來，搜尋時就變得還要再多找串列裡的內容，
讓原本這類方法搜尋速度很快的優勢少了很多。&lt;/p&gt;
&lt;img alt=&quot;hash-with-linked-list.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2646cc0e-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;為了解決這個問題 Bloomier Filter 提供了一個很有趣的解決方法，它使用了和 Bloom Filter 相同的概念，
也就是運用多個 Hash 函式來減少發生「碰撞」的可能性。&lt;/p&gt;
&lt;p&gt;具體來說，「碰撞」的問題在於不同的 Key 計算出來的 Hash 值是一樣的，所以無法利用 Hash 函式區別不同的 Key。
那為什麼利用多個 Hash 函式就可以減少「碰撞」的機會呢？道理很簡單，假設現在我用三個 Hash 函式來建置 Hash Table，
那麼理論上一個 Key 就會對應到 Hash Table 上的三個位置，當我發現我第一個位置有人用了，我只要放第二個位置即可，
如果第二個位置一樣很倒楣，也被其他的 Key 使用了，那就放第三個位置，這樣發生「碰撞」的機會就會變小很多。&lt;/p&gt;
&lt;p&gt;不過這種方式會產生新的問題，因為三個位置都有可能是 Key 存放資料的地方，
所以搜尋的時候就必須一個個檢查才能判斷資料真正存放的位置，一樣會增加搜尋的時間。
而且也因為我要確認某個位置是那一個 Key 所存放的資料，
所以我還必須多加一個欄位來紀錄 Key 的資訊，多佔許多空間。&lt;/p&gt;
&lt;p&gt;所以 Bloomier Filter 並不是這麼使用多個 Hash 函式的。&lt;/p&gt;
&lt;p&gt;我們先仔細觀察一下現在的情況，雖然 Key 對應的三個位置，但只需要用到一個位置來存放資料，
其他位置裡頭的內容是什麼其實無關緊要。現在的問題是我不知道那個 Hash 函式對應的位置才有我要的資料......
所以如果我能找到一個方法「可以在不知道資料實際存放資料的情況下取得資料」，
那我是不是就可以不用煩惱資料到底放在那個 Hash 函式對應的位置，
也不用煩惱如何建置「能夠判斷某一位置的資料對應那一個 Key」的資料結構了？&lt;/p&gt;
&lt;p&gt;這種猶如魔法般的想法有沒有可能實現呢？Bloomier Filter 告訴你，確實是有的－－使用 XOR(Exclusive OR)。&lt;/p&gt;
&lt;p&gt;XOR 的特性很有趣，假設 A xor B 會得到 C，那 B xor A 也會得到相同的值 C，而 C xor A 會得到 B，
C xor B 會得到 A。&lt;/p&gt;
&lt;p&gt;看不明白的話，參看下表：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
A xor B =&amp;gt; C
C xor A =&amp;gt; B
C xor B =&amp;gt; A
&lt;/pre&gt;
&lt;p&gt;因為這個特性，所以 XOR 在密碼學中很常用，比如說：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
資料 xor Key =&amp;gt; 密文
密文 xor key =&amp;gt; 資料
&lt;/pre&gt;
&lt;p&gt;如果沒有 Key ，就算拿到密文也不知道真正的資料。
但如果有 Key 的話，就可以對密文做 XOR 運算取得所要的資料。&lt;/p&gt;
&lt;p&gt;那麼 XOR 究竟要怎麼應用在 Bloomier Filter 上呢？
仔細看看下表，這是用三個變數的情況：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
A xor B xor C =&amp;gt; D
D xor A xor B =&amp;gt; C
&lt;/pre&gt;
&lt;p&gt;也許你已經看出來了，若還是看不出來的話，我就再改一下：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
資料 xor B xor C =&amp;gt; D
D xor B xor C =&amp;gt; 資料
&lt;/pre&gt;
&lt;p&gt;套用到 Bloomier Filter 上，那就是現在有三個 Hash 函式，而 B、C 和 D 都是某一個 Key 在 Hash Table 上對應位置的內容。
接下來搜尋的時候，只要把三個位置的內容全部 xor 起來，就可以直接得到資料了。也就是說，
現在我再也不用在意到底那個位置才是實際放資料的位置，只要做 XOR 運算就好了。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;div class=&quot;linenodiv&quot;&gt;&lt;pre&gt;&lt;span class=&quot;normal&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;normal&quot;&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#假設使用三個 Hash 函式的情形下&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hash1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hash2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hash3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;至於建置 Hash Table 的時候也很簡單，只要把除了實際放資料的位置外，其餘所有位置對應的內容和資料做 XOR 運算後，
再存進實際放資料的位置即可。&lt;/p&gt;
&lt;img alt=&quot;bloomier-filter.png&quot; src=&quot;https://blog.marco79423.net/backend/static/26463c1c-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;div class=&quot;linenodiv&quot;&gt;&lt;pre&gt;&lt;span class=&quot;normal&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;normal&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;normal&quot;&gt;3&lt;/span&gt;
&lt;span class=&quot;normal&quot;&gt;4&lt;/span&gt;
&lt;span class=&quot;normal&quot;&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#實際要放資料的位置 = 資料 xor (其餘位置的內容1 xor 其餘位置的內容2....)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;hash&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hash1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actual_position&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hash_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actual_position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;這麼一來，Bloomier Filter 大致的概念就清楚了。不過實際上要怎麼建置這個 Hash Table 呢？這就是一件麻煩事了。&lt;/p&gt;
&lt;p&gt;建置的要點就是為每一個 Key 對應的資料找到在 Hash Table 實際存放的位置，
而這個位置不可以和其他 Key 共用，也就是說一個位置只能放一個 Key 的資料。不過另一方面，
只要不放入實際的資料，其他 Key 還是可以參照這個位置的內容，因為除了實際要存放資料的位置外，
其餘位置裡的內容只是會用到，並不會有任何改變。&lt;/p&gt;
&lt;p&gt;實際在建置 Hash Table 時，大致只會有三個步驟：&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;找出 Key 在 Hash Table 對應的所有位置，然後紀錄 Hash Table 中的每一個位置有那些 Key 對應對到。&lt;/li&gt;
&lt;li&gt;檢查 Hash Table 每一個位置，把只有一個 Key 使用的位置抓出來，當成該 Key 實際存放資料的位置。
紀錄完這個資訊後，再把第一步有關該 Key 的紀錄清除。這樣一來，可能有些位置就會變成只有一個 Key 使用。
不斷重覆這個步驟，直到找出每一個 Key 實際存放資料的位置為止。&lt;/li&gt;
&lt;li&gt;最後再依照找出 Key 的順序反向放進 Hash Table。放入的方式就是前述的方法，
將所有不是實際放資料的位置裡的內容和資料全部 XOR 起來，存進實際放資料的位置裡。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第三步之所以要反向放進 Hash Table 是因為很可能別的 Key 要參照的位置，是某些 Key 實際存放資料的位置，
如果不照順序的話，就會影響到其他的 Key。&lt;/p&gt;
&lt;p&gt;嚴格說起來，這個演算法就是利用暴力解的方式找到每一個 Key 實際要放資料的位置，所以建置的效率並不太好，
而且更新也非常麻煩。&lt;/p&gt;
&lt;p&gt;事實上，這個演算法還有一個問題，那就是在很罕見的情況下，可能會發生每一個位置都不只有一個 Key 使用的情況，
這樣我在第二步的時候，就會發生不知道該把那一個 Key 抓出來的情況。
這時一個簡單的解決辦法就是隨便把一個 Key 抓出來丟掉，一直丟到問題解決為止。&lt;/p&gt;
&lt;p&gt;聽起來，這個方法很弱，因為這樣一來，有些 Key 就放不進 Hash Table 了。
但事實上，這種情況發生的機率極低，而且我也可以增加 Hash Table 的大小之類的手法解決。
若還是有放不進 Hash Table 的情況的話，我也可以用其他資料結構補助，反正沒多少，所以影響不會很大。&lt;/p&gt;
&lt;p&gt;雖然有這些缺點，但 Bloomier Filter 做到能一致而且快速的搜尋 Key，
而且保持了 Hash 系列方法中，佔的空間很小的好處，因此還是有很高的討論價值。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[IP Lookup 演算法 - Multibits Trie]]></title>
        <id>https://blog.marco79423.net/articles/ip-lookup-%E6%BC%94%E7%AE%97%E6%B3%95-multibits-trie/</id>
        <link href="https://blog.marco79423.net/articles/ip-lookup-%E6%BC%94%E7%AE%97%E6%B3%95-multibits-trie/"/>
        <updated>2013-05-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[「Binary Trie」這個演算法非常簡單易懂，而且也相當好實作。但在搜尋的速度上，
仍有許多待改進的空間，最大的問題在於「Binary Trie」這個演算法建立的二元搜尋樹太深了！
1 個位元一層， 32 個位元就可能要往下 32 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;「Binary Trie」這個演算法非常簡單易懂，而且也相當好實作。但在搜尋的速度上，
仍有許多待改進的空間，最大的問題在於「Binary Trie」這個演算法建立的二元搜尋樹太深了！
1 個位元一層， 32 個位元就可能要往下 32 層，也就是說可能就要比較 32 次才會得到結果，
自然效果不太好。&lt;/p&gt;
&lt;p&gt;於是有人問了：「為什麼要這麼麻煩呢？既然一個位元一個位元比較速度不夠快，
那一次比較好幾個位元不就得了？」&lt;/p&gt;
&lt;p&gt;於是「Multibits Trie」這個演算法因應而生，它的概念和「Binary Trie」沒什麼不同，
差別在「Multibits Trie」會一次比較多個位元來減少層數，用這種方法增加搜尋的速度。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/2212e60e-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/2212e60e-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;「stride」代表一次看幾個位元，所以上圖左代表「stride」為 2 的樹。
理論上「stride」值越多，樹的層數也會相應得變少。&lt;/p&gt;
&lt;p&gt;不過這樣一來，就會產生新的問題，其實從圖中就可以看得出來，雖然層數變少了，
但是節點數卻增加了！換言之，就是所需要的記憶體增加了！為什麼會發生這種情況呢？
原因很簡單，因為本來「Binary Trie」是一個位元一個位元比較，
一直比到 Prefix 的長度為止(因為超過 Prefix 的長度的話，之後不管值是什麼都沒關係)。&lt;/p&gt;
&lt;p&gt;但「Multibits Trie」是一次看數個位元，很可能會碰到要比較位元已經超過 Prefix 長度的情況，
好比說我一次看 2 個位元，但後面的位元本來是不用看的。&lt;/p&gt;
&lt;p&gt;如下表&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;33%&quot; /&gt;
&lt;col width=&quot;67%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;Prefix&lt;/td&gt;
&lt;td&gt;Nexthop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1***&lt;/td&gt;
&lt;td&gt;粉紅色的出口&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;(這個例子中，只要看 1 個位元即可，後面都不需要看，但若我是要 2 個位元 2 個位元看，
第 2 個位元就超過 Prefix 長度了)&lt;/p&gt;
&lt;p&gt;那怎麼辦呢？解決問題的方法也很簡單，比如說剛剛的例子，
只要列出所有的長度既是「stride」的倍數，而且又能包含原來 Prefix 的所有可能的新 Prefix 即行了。&lt;/p&gt;
&lt;p&gt;像剛才的例子，就可以用新的兩條規則取代舊的規則，如下表&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;32%&quot; /&gt;
&lt;col width=&quot;68%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;Prefix&lt;/td&gt;
&lt;td&gt;Nexthop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;10**&lt;/td&gt;
&lt;td&gt;粉紅色的出口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;11**&lt;/td&gt;
&lt;td&gt;粉紅色的出口&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;這樣一來，意義不變，但是卻可以方便我們建立節點。&lt;/p&gt;
&lt;p&gt;不過用這種方法又會產生新的問題，因為規則的 Prefix 是我們自己變長的(上例是從 1 變 2)，
所以若碰到原來 Prefix 就是 2，而且又是共用同一個節點存放「Nexthop資訊」時，
就會有優先權的問題。幸好這個問題也很好解決，只要紀錄原來的 Prefix 長度，
等到碰到共用同一個節點的情況時，比較原來的 Prefix 長度，把舊的蓋掉即可。&lt;/p&gt;
&lt;p&gt;關於搜尋的範例程式碼&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#預設值，此例直接把預設值放在 root 中。&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#Binary Trie 時是一次 shift 1，現在是一次 stride 個&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;若比較「Binary Trie」的搜尋的程式碼，就會發現兩者非常相似，
差別在於一次看的的位元數目不同而已，還有「Binary Trie」每個節點都只會有兩個子節點，
「Multibits Trie」則會因「stride」不同而有不同的子節點數目僅此而已。&lt;/p&gt;
&lt;p&gt;因此在「Binary Trie」中，我取「left」、「right」代表左子節點和右子節點，
而在「Multibits Trie」時，因為不會只有兩個子節點，所以用串列「children」代表子節點。&lt;/p&gt;
&lt;p&gt;接下來是建立樹的部分，概念上和「Binary Trie」大同小異，只是多了兩個關鍵點，分別是：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;把長度不是「stride」的倍數的 Prefix 展開成多個 Prefix&lt;/li&gt;
&lt;li&gt;碰到 Prefix 長度相同的規則比較其優先權(自己變的當然優先權會比較小)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_tree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entrys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;#prefix, nexthop 表各別的規則&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entrys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# /0 表預設值&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#紀錄 prefix 的長度，之後可以用來比較優先權&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;#bit_count 表不足一個 stride 的位元個數&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;bit_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;#把長度不是「stride」的倍數的 Prefix 展開成多個 Prefix&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;new_prefixs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bit_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bit_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;new_prefix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;new_prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bit_count&lt;/span&gt;

                    &lt;span class=&quot;n&quot;&gt;new_prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;new_prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

                    &lt;span class=&quot;n&quot;&gt;new_prefixs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;new_prefixs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_prefix&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_prefixs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_ip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

                &lt;span class=&quot;c1&quot;&gt;#用 prefix 的長度比較優先權&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;「Multibits Trie」演算法可以有效的增加搜尋的速度，不過代價是必須要付出更多的記憶體。
這個演算法非常實用，建立、更新、搜尋都有不錯的效果，根據我導師的說法，雖然這個演算法非常基本，
但現在業界其實都是只在用這種演算法而已。&lt;/p&gt;
&lt;p&gt;所以說，有研究相關領域的人，這個演算法鐵定要非常熟悉才是。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[IP Lookup 演算法 - DIR-24-8-BASIC]]></title>
        <id>https://blog.marco79423.net/articles/ip-lookup-%E6%BC%94%E7%AE%97%E6%B3%95-dir-24-8-basic/</id>
        <link href="https://blog.marco79423.net/articles/ip-lookup-%E6%BC%94%E7%AE%97%E6%B3%95-dir-24-8-basic/"/>
        <updated>2013-05-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這個演算法是「Routing Lookups in Hardware at Memory Access Speeds」這篇論文提出的，
除了最基本的「DIR-24-8-BASIC」，還有其兩種變化，分別是「DIR-24-8-INT ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;這個演算法是「Routing Lookups in Hardware at Memory Access Speeds」這篇論文提出的，
除了最基本的「DIR-24-8-BASIC」，還有其兩種變化，分別是「DIR-24-8-INT」和「DIR-n-m」&lt;/p&gt;
&lt;p&gt;「DIR-24-8-BASIC」演算法設計有兩個表格，分別稱作「TBL24」和「TBLlong」。
「TBL24」表格裡頭有 2 ^ 24 個項目，其索引對應著 IP 前 24 位元 0.0.0 到 255.255.255 共 2 ^ 24 種可能。
而索引對應的值和 IP 後 8 個位元經過計算後，可以當成「TBLlong」的索引，
「TBLlong」存放的內容便是 Nexthop 資訊了。&lt;/p&gt;
&lt;p&gt;而「TBLlong」的存放的 Nexthop 資訊都來自於長度超過 24 的 prefix，
若是少於 24 的 prefix ，為了減少搜尋的時間，其對應的 Nexthop 會直接存在「TBL24」這個表格中，
而不是放在「TBLlong」。所以說「TBL24」可能會有兩種資訊，
一種是「TBLlong」的索引」，另一種則是 Nexthop 的資訊。&lt;/p&gt;
&lt;img alt=&quot;tbl24.png&quot; src=&quot;https://blog.marco79423.net/backend/static/25a3946c-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;由「TBL24」的存放的內容第一個位元可以得知後面十五個位元實際存放的資訊是什麼。&lt;/p&gt;
&lt;p&gt;而搜尋的過程是把要做搜尋的 IP 的前 24 個位元當成「TBL24」的索引，找到對應的值後，
若發現值的第一個位元是 0，搜尋就直接停止並回傳 Nexthop 資訊，若發現不是，
再根據得到的值和 IP 後 8 個位元「接在一起」算出「TBLlong」的索引值，
然後在「TBLlong」找到對應的 Nexthop 資訊，完成這次的搜尋。&lt;/p&gt;
&lt;img alt=&quot;tbllong.png&quot; src=&quot;https://blog.marco79423.net/backend/static/25a3c5a4-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;在實作時要注意，對於所有長度小於 24 的 prefix，都必須展開成許多長度為 24 的 prefix，
因為我們是用 IP 的前 24 個位元當成索引，自然不能少於 24 才行。&lt;/p&gt;
&lt;p&gt;至於 TBLlong 的索引的計算方式是當碰到 prefix 長度大於 24 的時候，利用從「TBL24」取得的值，乘上　256(等於往左平移 8 位元) 再加上 IP 後面八個位元，便是 TBLlong 的索引。
我們可以利用該索引取得 Nexthop 資訊。&lt;/p&gt;
&lt;p&gt;TBLlong 包含了所有長度大於 24 的 prefix。而因為剩於 8 個位元，
每個 prefix 都要考慮 256 種狀況，
所以 TBLlong 實際上會有「長度大於 24 的 prefix 的個數」乘上 256 個項目來紀錄 Nexthop 資訊。&lt;/p&gt;
&lt;img alt=&quot;dir-24-8-basic.png&quot; src=&quot;https://blog.marco79423.net/backend/static/25a34cd2-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;至於實作的部分.....我懶了，改天再說吧。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Bloom Filter]]></title>
        <id>https://blog.marco79423.net/articles/bloom-filter/</id>
        <link href="https://blog.marco79423.net/articles/bloom-filter/"/>
        <updated>2013-05-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Bloom Filter 是一種可以儲存「某一個元素是否存在」的集合，
我們可以用這種資料結構快速查詢像是「某隻小雞是否在這間雞舍」或「某位學生是不是在這間實驗室」
這一類的資訊。
這種資料結構有三個非常重要的特性：

不存在漏報(False Negative)：有一定會說 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;Bloom Filter 是一種可以儲存「某一個元素是否存在」的集合，
我們可以用這種資料結構快速查詢像是「某隻小雞是否在這間雞舍」或「某位學生是不是在這間實驗室」
這一類的資訊。&lt;/p&gt;
&lt;p&gt;這種資料結構有三個非常重要的特性：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;不存在漏報(False Negative)：有一定會說&lt;/li&gt;
&lt;li&gt;但卻可能誤報(False Positive)：但說了不一定會對&lt;/li&gt;
&lt;li&gt;確定某元素是否在集合的代價和元素數目無關：不管對不對，反正很快&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這個結構有趣的地方在於它並不準確，它可能會誤報，也許它會說：「這隻小雞在這間雞舍！」，
但實際上這隻小雞並不在這間雞舍。不過反過來說，若它說：「這隻小雞『不在』這間雞舍！」，
那小雞就絕對不會出現在這間雞舍中。&lt;/p&gt;
&lt;p&gt;雖然這種資料結構可能不準確，但查詢速度卻非常快，所以很適合用來當作事前的篩選。
舉例而言，若我想找「某隻小雞在那一間雞舍的資訊」，就可以透過 Bloom Filter 對每一間雞舍做篩選，
篩去 Bloom Filter 說「這隻小雞不在這間雞舍」的雞舍，再對剩下的雞舍做詳細的調查，
這樣就會比漫無目的找還要快上許多。&lt;/p&gt;
&lt;p&gt;一般而言，一個搜尋演算法，若搜尋的速度要很快，資料結構所佔的空間就會相對比較大，
但若要求佔的空間要小，速度可能就快不起來。基本上，好的演算法就是取得空間和速度的平衡點，
讓速度足夠快，而且空間也不會用太多。&lt;/p&gt;
&lt;p&gt;但 Bloom filter 卻很神奇的既速度快，而且所佔的空間也很小，不過代價是用準確率換取的空間大小。
空間越大，準確率越高，空間越小，準確率就越低。&lt;/p&gt;
&lt;p&gt;「那麼這個方法到底是怎麼做到這一點的呢？」&lt;/p&gt;
&lt;p&gt;這個方法是由一個叫 Bloom 的人提出的(顯而易見吧？)，
它實際上是一個很長的 bit-vector，和很多個 Hash 函式組成。
演算法的最初的概念也很簡單，想要找某元素存不存在？
那就準備一個 bit-vector，再使用 Hash 函式為每個元素找對應的位置。
如果該元素存在，即設為 1，如果不存在，就設為 0。
那麼以後想查詢某元素存不存在，只要看 Hash 函式編碼後對應的位置內容是不是 1 即可。&lt;/p&gt;
&lt;p&gt;很簡單吧？不過若要使用 Hash 函式的話，就會有一個很重要但很基本問題一定要考慮，
那就是 Key 和 Key 之間可能會有「碰撞」的問題，而一旦發生「碰撞」，就有可能誤報，
明明不存在，卻回報存在。&lt;/p&gt;
&lt;p&gt;這個問題最直觀的解決辦法就是增加 bit-vector 的大小，但是增加大小就會佔用更多的空間，
這樣就會違反 Bloom filter 要佔很小空間的初衷了。&lt;/p&gt;
&lt;p&gt;所以 Bloom 實際上採用的是另一種解法，那就是使用多個 Hash 函式。&lt;/p&gt;
&lt;p&gt;「一個 Hash 函式發生碰撞很容易，但若使用多個不同的 Hash 函式，
要每一個 Hash 函式對應的位置都剛好同時碰撞就很不容易了吧？」&lt;/p&gt;
&lt;p&gt;若使用多個 Hash 函式對應到 bit-vector 上，因為要每一個對應到的都是 1 才會符合，
只要有其中一個 Hash 函式對應到的位置內容不為 1，就一定不會存在，所以相對於原本的方法，
「誤報」的可能性自然會比較低。不過這並不代表不會發生碰撞，所以仍然有「誤報」的可能。&lt;/p&gt;
&lt;p&gt;這個方法的好處是可以在不增加 bit-vector 的大小而且保持搜尋速度的情況下，
有效的減少了誤報的機會。&lt;/p&gt;
&lt;p&gt;由於 Bloom Filter 其實就是使用 Hash 函式，所以搜尋的速度很快，
而且所佔的空間也很小(Hash 方法的特性)。不過這個方法雖然好，但還是有一些缺點，
其中最重要的就是不支援刪除，由於採用多個 Hash 函式的原因，
所以我們不敢隨便把一個 Hash 函式對應的位置從 1 改成 0，
因為這有可能會影響別的 Hash 函式。&lt;/p&gt;
&lt;p&gt;很多地方都可以用到 Bloom Filter ，比如說 IP Iookup。使用的概念主要都是
「為了增加搜尋的速度，於是在搜尋的過程中另外添加一個篩選器，先過濾掉不可能的結果，
再做搜尋，這樣就可以增加搜尋的速度。」，如下圖：&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/25ac2668-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/25ac2668-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;另外還有很多不同的運用和延伸，這之後再研究吧。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[IP Lookup 演算法 - Binary Trie]]></title>
        <id>https://blog.marco79423.net/articles/ip-lookup-%E6%BC%94%E7%AE%97%E6%B3%95-binary-trie/</id>
        <link href="https://blog.marco79423.net/articles/ip-lookup-%E6%BC%94%E7%AE%97%E6%B3%95-binary-trie/"/>
        <updated>2013-05-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Binary Trie 恐怕是 IP lookup 最簡單的演算法，它的概念非常直覺，也非常好懂。

為了方便說明，所以假設 IP 只會有 4 個位元(實際上 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;Binary Trie 恐怕是 IP lookup 最簡單的演算法，它的概念非常直覺，也非常好懂。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/26ca1a46-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/26ca1a46-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;為了方便說明，所以假設 IP 只會有 4 個位元(實際上 IPv4 會有 32 個位元)，
上圖代表「Binary Trie」的架構圖，圖左是所建立的「Binary Search Tree」，
圖右是路由表的規則，如第一項是「0 *」，代表「0 * 時就會丟到相應的 Nexthop」，
在此使用不同的顏色表示相應的「Nexthop」，
如紫色就表示會走紫色的「Nexthop」 (實際上自然不會用顏色判斷)。
而左邊的則是依照路由表所建立的二元樹，節點上有 0、1 兩種數字，
而有顏色的節點則代表有「Nexthop 資訊」的節點。&lt;/p&gt;
&lt;p&gt;「Binary Trie」的運作方式很簡單，這個演算法會建立一個二元樹，
而有些節點會包含有關「Nexthop 資訊」(也就是圖中的有顏色的節點)，
當新的 IP 進來時，就會從 IP 的最左邊往右開始比對，從樹的根節點開始，
碰到 0 便往左子節點，到到 1 便往右節點，以此類推，一直到葉節點或沒有對應節點為止，
這時中間經過的節點中(包含最後一個節點)，
最後一次出現的「Nexthop資訊」就是我們要的答案。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/26ca63de-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/26ca63de-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;為什麼要是最後一個有「Nexthop 資訊」的就是答案呢？
其實中途只要碰到有「Nexthop 資訊」的節點，
就表示無論接下來的子節點是什麼都還是會符合該條規則，
只是因為路由器搜尋的規則是「Longest prefix match」,也就是說單是符合規則還不夠，
我們還要最長、最精確的那一項，所以才會選擇最後一次碰到的結果。&lt;/p&gt;
&lt;p&gt;上圖中，綠色的規則因為比較長，所以會比紫色的還晚得到結果。&lt;/p&gt;
&lt;p&gt;完整的搜尋程式碼大致如下&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search_binary_search_tree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#預設值，此例直接把預設值放在 root 中。&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;ip_length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#若為真實世界的 ip 則是 32&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ip_length&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;#碰 0 找左子節點，碰 1 找右子節點&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ip_length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;#若有 nexthop 資訊，則紀錄下來&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;知道搜尋的原理，那就開始建立二元樹了。&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_binary_search_tree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entrys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BinarySearchTreeNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;#prefix, nexthop 表各別的規則&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entrys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;#通常 /0 表預設值，此例中，預設值直接放在 root.nexthop 中&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;#如果超過 prefix 表 don&amp;#39;t care&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

                &lt;span class=&quot;c1&quot;&gt;#遇 0 往左，遇 1 往右&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BinarySearchTreeNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BinarySearchTreeNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;#紀錄 nexthop 資訊&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nexthop&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建立的方式相當容易了解，也就是照著路由表一個個往下建樹的節點，
直到建完為止。這個方法雖然簡單，但在最糟的情況下，樹高會是 32 ，
換句話說，可能要往下走 32 層才能找到我要的資訊，所以這個方法雖然簡單，
但效率並不好。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[UNIX 常用指令 - less]]></title>
        <id>https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-less/</id>
        <link href="https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-less/"/>
        <updated>2013-04-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。
在 FreeBSD 上，如果我們想看一份文件檔的內容，而且又不想要殺雞用牛刀的用文字編輯器(如：vim)來看的話，
那麼 ...]]></summary>
        <content type="html"><![CDATA[&lt;p class=&quot;rubric&quot;&gt;這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。&lt;/p&gt;
&lt;p&gt;在 FreeBSD 上，如果我們想看一份文件檔的內容，而且又不想要殺雞用牛刀的用文字編輯器(如：vim)來看的話，
那麼 cat 指令就是一個不錯的選擇.....嗎？不是，或者說很多情況不是。&lt;/p&gt;
&lt;p&gt;理由很簡單，因為這篇文章的標題不是「FreeBSD 常用指令 - cat」......喔！不是，是因為一般來說，
文件檔的內容常常都會超過一個畫面的大小，除非你是「神之眼」，不然前面的內容「咻~」一下就過了，
誰看得清楚？所以就這個例子來說，我們通常會改用 more 或是 less 來取代 cat。&lt;/p&gt;
&lt;p&gt;more 和 less 就功能而言，其實和 cat 很像，一樣是將文件的內容輸出到螢幕上。
不過不像 cat 會一次性的把所有內容都丟到螢幕上，而是顯示完一整個畫面的內容就停住，
然後讓你可以透過方向鍵上下捲動來查看剩餘的內容。&lt;/p&gt;
&lt;p&gt;比如說假如有一個檔案叫做「小雞的美食清單」，裡面內容很多，沒辦法用一個畫面顯示，這時我們就可以這樣下指令：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; less 小雞的美食清單
&lt;/pre&gt;
&lt;p&gt;這樣一來，畫面就會停在文件的最開頭，然後等看到畫面的內容看完了，
再依使用者的要求捲動頁面(使用方向鍵上下或用 s 和 j 上下)。&lt;/p&gt;
&lt;p&gt;若想離開這個狀態，只要再按下 q 即可來離開。&lt;/p&gt;
&lt;p&gt;至於 less 和 more 的差別是什麼呢？誰管他？
這些指令是拿來用的，不是來背歷史的。只要記得一件事：&lt;/p&gt;
&lt;p&gt;&lt;em&gt;「less is more than more」&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;這句話的意思大致是說 less 有的功能比 more 還多，
所以說 less 可以說是 more 的進階版，所以用 less 就對了。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;實際上因為沒道理把兩種版本卻功能一樣的指令留下來，
所以很多作業系統的 less 和 more 其實是同一個的檔案(如：FreeBSD)。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; diff /usr/bin/less /usr/bin/more
&lt;/pre&gt;
&lt;p class=&quot;last&quot;&gt;不信的話，可以用上面的指令試試看，就可以發現是兩個指令是一樣的內容(FreeBSD)。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;老樣子，下面是常用的功能列表：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;24%&quot; /&gt;
&lt;col width=&quot;76%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;按鍵&lt;/td&gt;
&lt;td&gt;功能說明&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;g&lt;/td&gt;
&lt;td&gt;回到文件最頂端&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;來到文件最底端&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;/&lt;/td&gt;
&lt;td&gt;往下搜尋關鍵字&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;往上搜尋關鍵字&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;:&lt;/td&gt;
&lt;td&gt;尋找某一行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;由於 less 的指令是來自 more 和 vi，所以熟悉 vi 的使用者，想必會對這些指令親切感才是。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[UNIX 常用指令 - wc]]></title>
        <id>https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-wc/</id>
        <link href="https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-wc/"/>
        <updated>2013-03-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。
wc 指令可以計算出檔案的列數、字數及位元數，比如說有一個檔案的叫做 chicken_house，
內容如下：

food food food ...]]></summary>
        <content type="html"><![CDATA[&lt;p class=&quot;rubric&quot;&gt;這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。&lt;/p&gt;
&lt;p&gt;wc 指令可以計算出檔案的列數、字數及位元數，比如說有一個檔案的叫做 chicken_house，
內容如下：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
food food food food food
food food food food food
&lt;/pre&gt;
&lt;p&gt;如果我想知道裡面究竟有多少 food 這個詞，我就可以這樣輸入指令：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; wc chicken_house
&lt;/pre&gt;
&lt;p&gt;就會輸出下面的結果：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
2 10 50 chicken_house
&lt;/pre&gt;
&lt;p&gt;第一個數字代表這個檔案總共有幾行，數字 2 代表總共有兩行，而第二個數字則代表字數，
因為總共有十個 food，所以是數字 10，至於第三個數字則是位元組數。&lt;/p&gt;
&lt;p&gt;一般來說，我們通常只會需要知道其中的一項資訊就夠了，比如說我想知道共有幾行，
或是共有幾個字，不需要把每一個資訊都列出來。碰到這種情況，我們可以加上一些參數來達成目的，
其中最常用的就是顯示行數，也就是加上參數 -l，加上後，就會只顯示行數，如下例：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
2 chicken_house
&lt;/pre&gt;
&lt;p&gt;順帶一提， wc 有幾行的判斷似乎是看最後有沒有 n 而定，所以最後一行可能不會被算進去，
這點最好要注意一下。最後一樣補上 wc 常用的參數列表：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;53%&quot; /&gt;
&lt;col width=&quot;47%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;-c, --bytes&lt;/td&gt;
&lt;td&gt;顯示位元組數&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;-m, --chars&lt;/td&gt;
&lt;td&gt;顯示字母數&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;-l, --lines&lt;/td&gt;
&lt;td&gt;顯示行數&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;-L, --max-line-length&lt;/td&gt;
&lt;td&gt;印出最長行的長度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;-w, --words&lt;/td&gt;
&lt;td&gt;顯示單字數&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[UNIX 常用指令 - sort]]></title>
        <id>https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-sort/</id>
        <link href="https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-sort/"/>
        <updated>2013-03-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。
sort 的功能就是將檔案的內容以行為單位做排序，非常實用，一定要會用。
假設有一個檔案名稱叫做 danger-level，內容為

marco   2 ...]]></summary>
        <content type="html"><![CDATA[&lt;p class=&quot;rubric&quot;&gt;這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。&lt;/p&gt;
&lt;p&gt;sort 的功能就是將檔案的內容以行為單位做排序，非常實用，一定要會用。&lt;/p&gt;
&lt;p&gt;假設有一個檔案名稱叫做 danger-level，內容為&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
marco   2
kevin   10
chicken 100
&lt;/pre&gt;
&lt;p&gt;代表各個使用者的危險層級，接下來我們會用這個例子來說明 sort 的用法。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; sort danger-level
&lt;/pre&gt;
&lt;p&gt;上面指令的意義就是為 danger-level 這個檔案中每一行做排序，因此結果會是：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
chicken 100
kevin   10
marco   2
&lt;/pre&gt;
&lt;p&gt;但通常每一行都會有很多欄位，比如說此例我們可能會想要以危險層級來排序，我們可以使用參數 -k，如：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; sort -k 2 danger-level
&lt;/pre&gt;
&lt;p&gt;就是表示 danger-level 從第兩個欄位(也就是數字部分)開始比較，結果如下：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
kevin   10
chicken 100
marco   2
&lt;/pre&gt;
&lt;p&gt;更詳細的用法，參數 -k 其實可以填第兩個位置，如「-k 2,2」，表示比較到這個欄位的意思，平常可以省略，
預設是最後一個欄位.....等一下！為什麼結果 100 會在 2 之前？難道是因為 marco 比 chicken 為危險？&lt;/p&gt;
&lt;p&gt;當然不是這樣的！因為 sort 使用的是字典排序，而「1」在「2」之前，所以會有這樣的結果，
如果想要 sort 以數值的方式來排序的話，就要加上參數 -n，如：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; sort -k 2 -n danger-level
&lt;/pre&gt;
&lt;p&gt;就會以危險數值由小排到大做排序：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
marco   2
kevin   10
chicken 100
&lt;/pre&gt;
&lt;p&gt;但有些時候，檔案的欄位並不是以空白或 tab 分割，比如說 /etc/passwd 是以「:」來分隔，
這時我們就要自行指定分隔的符號是什麼，不然 sort 並不會知道到底要以什麼符號分隔。
我們可以用參數 -t 指定要分隔的字元是什麼，來達成這個目的。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; sort -t: -k1 /etc/passwd
&lt;/pre&gt;
&lt;p&gt;除此之外，還有一個常用的參數是 -r，和許多常用的指令相同，即是反向排序。&lt;/p&gt;
&lt;p&gt;最後再補充兩個簡單而且常用的案例，第一個案例是將資料夾的檔案根據檔案大小做降序排列：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; ls -al | sort -k 5 -r -n
&lt;/pre&gt;
&lt;p&gt;第二個案例則是將 /etc/passwd 的內容依名稱排序，並去掉開頭是「#」的行，可以這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; sort -t: -k1,1 /etc/passwd | grep -v ^#
&lt;/pre&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[UNIX 常用指令 - cut]]></title>
        <id>https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-cut/</id>
        <link href="https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-cut/"/>
        <updated>2013-03-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。
cut 算是一個還滿常用的重要指令，功能是將文字檔以行為單位，切割出每一行的某幾個欄位。
聽起來很抽象對不對？舉個實際的例子，假設有一個叫做「personal_data」的檔案內容如下 ...]]></summary>
        <content type="html"><![CDATA[&lt;p class=&quot;rubric&quot;&gt;這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。&lt;/p&gt;
&lt;p&gt;cut 算是一個還滿常用的重要指令，功能是將文字檔以行為單位，切割出每一行的某幾個欄位。
聽起來很抽象對不對？舉個實際的例子，假設有一個叫做「personal_data」的檔案內容如下：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
姓名    身高 體重     身材      欄位真實性
chicken 高挑 身輕如燕 凹凸有致  (禁止)
&lt;/pre&gt;
&lt;p&gt;這時，一個叫 chicken 的小姐出現了，她仔細看了這份文件後，認為最後一欄「欄位真實性」沒有存在的必要，
只需要顯示前面四欄資料即可，不需要全部列出來，這時她只要這麼下指令：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; cut -f 1-4 personal_data
&lt;/pre&gt;
&lt;p&gt;就會只顯示「姓名」、「身高」、「體重」「身材」這四欄結果，達成她的目的。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
姓名    身高 體重     身材
chicken 高挑 身輕如燕 凹凸有致
&lt;/pre&gt;
&lt;p&gt;從這個例子可以很顯而易見的看出來，參數「-f」代表要切的欄位，由左至右，由 1 開始，
中間可以加上「-」代表範圍，「1-4」 代表由 1 到 4 的範圍。&lt;/p&gt;
&lt;p&gt;而若表示非連續的欄位，也可以使用「,」，比如說若 chicken 覺得除了最後一欄外，
第三欄的內容也「有一點點」不太符合事實，有一點心虛，所以想去掉這一欄，那麼她可以這麼下指令：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; cut -f 1-2,4 personal_data
&lt;/pre&gt;
&lt;p&gt;這樣就會只顯示：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
姓名    身高 身材
chicken 高挑 凹凸有致
&lt;/pre&gt;
&lt;p&gt;但要注意的一點是「 &lt;strong&gt;cut 是以 tab 鍵來當作分隔符號&lt;/strong&gt; 」，
所以如果文件的內容是用「空格」或是「:」來分隔欄位的話，
則必須自行指定分隔符號才行，這個時候就可以使用參數「-d」。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; cut -d &amp;quot; &amp;quot; -f 1-2,4 personal_data
&lt;/pre&gt;
&lt;p&gt;這個例子即表示以「空格」當作分隔符號的意思，而除了指定欄位用參數「-f」外，
也可以使用參數「-c」直接指定要切出那些字元，但因為用法和用參數「-f」很相似，
而且比較不常用(我個人認為)，所以這裡就不多提了。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;我發現在 FreeBSD 和在 Ubuntu 使用 cut 時，參數「-c」的行為有很多不同之處，這也是我不想討論的原因。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;這邊再列舉一個常用的用法，比如說若 chicken 小姐想列出自己的 ID、UID 、家目錄和 Shell，她可以這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; grep marco /etc/passwd | cut -f1,3,6-7 -d:
&lt;/pre&gt;
&lt;p&gt;就會顯示：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
chicken:1002:/home/chicken:/bin/tcsh
&lt;/pre&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;補充一點，若要用 awk 達成同樣的效果的話，可以這麼做&lt;/p&gt;
&lt;pre class=&quot;last doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; grep chicken /etc/passwd | awk -F: &#39;{printf &amp;quot;%s:%s:%s:%s&amp;quot;, $1, $3, $6, $7}&#39;
&lt;/pre&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[UNIX 常用指令 - grep]]></title>
        <id>https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-grep/</id>
        <link href="https://blog.marco79423.net/articles/unix-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4-grep/"/>
        <updated>2013-03-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。
grep 是非常重要的指令，可以用來抓取檔案某一行的內容。這個功能非常有用，
舉例來說，假設我想搜尋裡系統上有沒有 chicken 小姐的帳號，我可以這麼做 ...]]></summary>
        <content type="html"><![CDATA[&lt;p class=&quot;rubric&quot;&gt;這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。&lt;/p&gt;
&lt;p&gt;grep 是非常重要的指令，可以用來抓取檔案某一行的內容。這個功能非常有用，
舉例來說，假設我想搜尋裡系統上有沒有 chicken 小姐的帳號，我可以這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; grep chicken /etc/passwd
&lt;/pre&gt;
&lt;p&gt;透過搜尋 /etc/passwd 這個檔案的內容有沒有 chicken 這個關鍵字來檢查有沒有這個帳號。
這麼一來，若有這個帳號，grep 就會把那一行列出來，若沒有，就不會輸出任何結果，輸出結果可能會像下面這樣：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
chicken:x:1000:1000:chicken,,,:/home/chicken:/bin/tcsh
&lt;/pre&gt;
&lt;p&gt;上面輸出代表系統上確實有親愛的 chicken 小姐的帳號，若什麼都沒有顯示，就表示沒有這個帳號。&lt;/p&gt;
&lt;p&gt;除此之外，為了方便從檔案找尋，有時我們也許同時還想知道符合條件的這一行(有 chicken 關鍵字的行)究竟在那一行？
我們就可以再加上參數 -n ，這個參數的意思是在前面新增行號的資訊，比如說：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; grep -n chicken /etc/passwd
&lt;/pre&gt;
&lt;p&gt;可能就會輸出下面這樣的結果：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
34:chicken:x:1000:1000:chicken,,,:/home/chicken:/bin/tcsh
&lt;/pre&gt;
&lt;p&gt;(最前面的 34 表示這一行的內容是在檔案的第 34 行)&lt;/p&gt;
&lt;p&gt;事實上，這個指令並不只能抓取單一檔案的內容，這個指令好用的地方在於它可以一次抓取多個檔案裡面的內容，
不用一個個找尋，我們就可以找出「有那些檔案的某一行有這些內容」的資訊。&lt;/p&gt;
&lt;p&gt;比如說我正在寫程式，我若想找這個資料夾有那些檔案的程式碼中有用到 eat() 這個函數，
就可以透過 grep 指令抓取有那些檔案的某一行有包含 eat() 這個字串，輕鬆達成目的。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; grep  &amp;quot;eat()&amp;quot; *.c
&lt;/pre&gt;
&lt;p&gt;這樣就會把這個資料夾所有包含 eat()  這個字串的每一行都印出來，非常方便。&lt;/p&gt;
&lt;p&gt;但若是我只是知道有那些檔案有 eat() 字串而已，這樣輸出的內容可能就太多了，
這時我們就可以加上 -l 這個參數，就會只印出找到符合條件的檔案名稱。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; grep -l &amp;quot;eat()&amp;quot; *.c      #-l 表示只印出檔案名稱，而不是內容
&lt;/pre&gt;
&lt;p&gt;若是想直接抓取某個資料夾裡面所有的內容(包含裡面的所有資料夾)，也可以加上 -r 做遞迴搜尋，如：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; grep -r -l &amp;quot;eat()&amp;quot; code/     #-l 表示只印出檔案名稱，而不是內容
&lt;/pre&gt;
&lt;p&gt;除此之外，我們也可以用 grep 來計數，依此算出某符合的行到底出現幾次。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; grep -n chicken /etc/passwd
&lt;/pre&gt;
&lt;p&gt;加上參數 -n 會在符合的行前面顯示出個數，這樣我們就可以知道一共有幾行符合，
不過要注意的是這是指行的次數，所以說若該行出現符合字串的次數不只一次，
它還是會只算一次，所以相對來說，這個功能就比較不實用。&lt;/p&gt;
&lt;p&gt;另外，參數 -v 也是非常實用的功能，其代表反轉選取，表示找出所有不符合的行。
比如說，要找出 /var/log/mylog 中，每一個沒有「error」這個字串的行，就可以這麼下：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; grep -v error /var/log/mylog
&lt;/pre&gt;
&lt;p&gt;這樣就會秀出沒有出現 error 字串的行了。&lt;/p&gt;
&lt;p&gt;最後再補充整理一下一些常見的功能：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;16%&quot; /&gt;
&lt;col width=&quot;84%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;-n&lt;/td&gt;
&lt;td&gt;多輸出行號&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;-l&lt;/td&gt;
&lt;td&gt;印出符合的檔名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;-c&lt;/td&gt;
&lt;td&gt;印出符合的行數&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;-v&lt;/td&gt;
&lt;td&gt;找出不符合的行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;-r&lt;/td&gt;
&lt;td&gt;遞迴找尋資料夾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;-i&lt;/td&gt;
&lt;td&gt;忽略大小寫&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[UNIX - 行程(Process)]]></title>
        <id>https://blog.marco79423.net/articles/unix-%E8%A1%8C%E7%A8%8Bprocess/</id>
        <link href="https://blog.marco79423.net/articles/unix-%E8%A1%8C%E7%A8%8Bprocess/"/>
        <updated>2013-03-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。
行程(Process)簡單來說就是正在運行中的程式。
每一支程式執行時，都會產生對應的新行程，而這些行程都有屬於自己的 Process ID(或稱 ...]]></summary>
        <content type="html"><![CDATA[&lt;p class=&quot;rubric&quot;&gt;這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。&lt;/p&gt;
&lt;p&gt;行程(Process)簡單來說就是正在運行中的程式。
每一支程式執行時，都會產生對應的新行程，而這些行程都有屬於自己的 Process ID(或稱 PID)來代表自己，
之所以不直接用程式名稱來代表是因為除了避免同名外，還因為同一支程式也可以同時執行很多次，
如果用程式名稱來代表的話，就不能區分了。&lt;/p&gt;
&lt;p&gt;每個 PID 都是獨一無二的非零正整數，由系統自動分配。
理論上不會同時有兩個運行中的行程有同樣 PID，對系統而言，同樣 PID 就代表是同一個行程。&lt;/p&gt;
&lt;p&gt;除了 PID 之外，還有 Parent Process ID(也就是 PPID)，這個數值代表其父行程的 PID。
父行程指的是產生這個行程的行程，聽起來很繞口吧？總之就是這個行程是由 PPID 所代表的行程產生的。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;所有的行程都可以追溯其父行程至 init 這個行程，
init 是系統開啟時第一個啟動的行程，&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;除了 PID 和 PPID 外，行程還有諸多屬性，比較重要的是 Nice 值，&lt;/p&gt;
&lt;p&gt;這邊要討論的是 Nice 值，也就是俗稱的好人值，好人值越低的，代表優先權越高，行程會被優先處理，
可能會越快完成；反過來說，好人值越高的行程，代表優先權越低，因為人太好了，
所以一有行程和它爭就會讓給別人，也因此資源越少，越多人爭，這個行程的任務就會越慢完成。&lt;/p&gt;
&lt;p&gt;一般來說，我們不太會介意一個需要執行一星期的程式晚一個小時完成，
但絕對會介意原本只需要五秒就能完成的程式花上十秒才完成。&lt;/p&gt;
&lt;p&gt;所以如果有一支程式需要執行很長的一段時間，就會把 Nice 值設得比較高。因為資源有限，
這種程式不需要一直和只需要十秒鐘內就可以完成的行程搶資源，因為一旦搶了，
這些十秒內就可以完成的行程就可能會比較慢才結束，這是我們不能容忍的。&lt;/p&gt;
&lt;p&gt;對 FreeBSD 而言，預設的行程的 Nice 值是 0，若想以不同的 Nice 值來執行程式，可以使用 nice 這個指令來指定其執行時的優先權：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; nice -n 15 /usr/local/bin/myjob
&lt;/pre&gt;
&lt;p&gt;但若這支程式已經在運行中的話，則可以用 renice 來重新指定其執行時的優先權：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; renice -n 15 /usr/local/bin/myjob
&lt;/pre&gt;
&lt;p&gt;(其中 15 指的是 Nice 值)&lt;/p&gt;
&lt;p&gt;FreeBSD 的 Nice 值範圍是 -20 ~ 20，不同的作業系統數值可能不一樣，但概念都是相同的，
對 FreeBSD 系統的一般使用者來說，Nice 值可以調整的範圍只能是 0 ~ 20，
也就是說一般使用者只能讓自己的程式設成低優先權，但卻不能自己調高行程的優先權，
只有 root 才能這麼做(再強調一次「root is god!」)。&lt;/p&gt;
&lt;p&gt;另外，行程有多種狀態條列如下(說實在的，不用去記，看久了就知道)：&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;16%&quot; /&gt;
&lt;col width=&quot;27%&quot; /&gt;
&lt;col width=&quot;58%&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead valign=&quot;bottom&quot;&gt;
&lt;tr&gt;&lt;th class=&quot;head&quot;&gt;狀態&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;意義&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;備註&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;I&lt;/td&gt;
&lt;td&gt;Idle&lt;/td&gt;
&lt;td&gt;表示沉睡了大約 20 秒左右&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;Runnable&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;Sleeping&lt;/td&gt;
&lt;td&gt;表示沉睡不到 20 秒&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;Stopped&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Z&lt;/td&gt;
&lt;td&gt;Zombie&lt;/td&gt;
&lt;td&gt;代表死掉的行程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;in Disk&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;這些符號代表這些行程執行的狀態，像是正在運作，或是等待等資訊，若想要知道更多，可以使用指令 ps 來查看(或直接找 Google 大神)。
上面只是列舉一部分的狀態, 詳情可以 man ps 並搜尋 state 的部分，就可以看到更詳細的資訊。&lt;/p&gt;
&lt;p&gt;以上。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[UNIX - 變成 Root]]></title>
        <id>https://blog.marco79423.net/articles/unix-%E8%AE%8A%E6%88%90-root/</id>
        <link href="https://blog.marco79423.net/articles/unix-%E8%AE%8A%E6%88%90-root/"/>
        <updated>2013-02-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。
「請不要忘記『Root is God』，所以登入 root 一定要有所限制」
在預設的情況下，root ...]]></summary>
        <content type="html"><![CDATA[&lt;p class=&quot;rubric&quot;&gt;這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;「請不要忘記『Root is God』，所以登入 root 一定要有所限制」&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在預設的情況下，root 不能直接由 ssh 連進去，但若想要從網路直接登入 root 的話，
可以將 /etc/ssh/sshd_config 這個設定檔中的 PermitRootLogin 改成 yes，
不過因為這樣不安全，所以最好不要這麼做。&lt;/p&gt;
&lt;p&gt;那麼要怎麼登入 root 呢？很簡單，那就是由另一個使用者變成 root！&lt;/p&gt;
&lt;p&gt;有兩種方式可以變成 root，第一種是使用指令 su，su 的功能就是變成某一使用者，
su 後面接使用者，就代表要變成該使用者，如要變成 chicken，就打下面的指令：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; su chicken
&lt;/pre&gt;
&lt;p&gt;變成該使用者必須知道這個使用者的密碼，所以不是說要變誰就變誰。&lt;/p&gt;
&lt;p&gt;若 su 後面不加使用者，就是變成　root，但要變成 root，除了要知道 root 密碼外，
當前的使用者必須要在 wheel 群組之中。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;su 後可加上 &amp;quot;-&amp;quot; 這個參數，就會模擬完整的登入。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;第二種方式則是使用 sudo，sudo 就像是有限制的 su，使用起來有很多好處，像是：&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;可以設定只在某個指令下擁有 root 權限&lt;/li&gt;
&lt;li&gt;使用會有 log&lt;/li&gt;
&lt;li&gt;使用者不用知道root 密碼&lt;/li&gt;
&lt;li&gt;比較快，不用登入成 root&lt;/li&gt;
&lt;li&gt;可以多台機器共用同一個 sudo 權限的設定檔&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而想要使用 sudo ，必須先用 visudo 來編輯 sudo 的權限，設定的格式如下：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
#使用者  主機=(以什麼身份執行)  可以下的指令
chicken  bsd1=(root)            /sbin/shutdown
&lt;/pre&gt;
&lt;p&gt;括號代表可以省略，所以說以什麼身份執行那一欄可以不填，預設即為 root，
至於填寫可以下的指令那一欄的時候，一定要用絕對路徑才行。&lt;/p&gt;
&lt;p&gt;有時，一項項設定太麻煩了，這時候可以用一些關鍵字來表示，可以自己設定，也可以用預設的，
比如說 ALL 代表「所有的」。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
root ALL=(ALL) ALL
&lt;/pre&gt;
&lt;p&gt;這就表示 root 可以在所有的主機上，以所有的身份執行所有的指令(不過這是廢話，就算不用 sudo，
root 也可以這麼做)。&lt;/p&gt;
&lt;p&gt;還有一點就是千萬要小心使用 ALL ，可以的話，最好還要再加上一些限制，比如說可以下的指令最好不要有 Shell，
不能有 su，不然使用者就可以直接變成 root 了，一旦變成 root，他做什麼事情就沒有 log 紀錄，
想做什麼就做什麼，太危險了。&lt;/p&gt;
&lt;p&gt;因為使用 sudo 有非常多好處，所以我們會更推薦使用 sudo，不過由於 FreeBSD 預設沒有安裝 sudo，
所以要使用 sudo 前，必須要先從 ports 安裝才行。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[UNIX - 禁止某使用者登入]]></title>
        <id>https://blog.marco79423.net/articles/unix-%E7%A6%81%E6%AD%A2%E6%9F%90%E4%BD%BF%E7%94%A8%E8%80%85%E7%99%BB%E5%85%A5/</id>
        <link href="https://blog.marco79423.net/articles/unix-%E7%A6%81%E6%AD%A2%E6%9F%90%E4%BD%BF%E7%94%A8%E8%80%85%E7%99%BB%E5%85%A5/"/>
        <updated>2013-02-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。
「身為一個專業的網管，有時將一個看不順眼的使用者停權也是非常合情合理的。」
有時我們會需要將某一個使用者停權，讓他暫時不能登入系統，
請注意！是「暫時」不能登入而已，所以不能直接刪除他的帳號 ...]]></summary>
        <content type="html"><![CDATA[&lt;p class=&quot;rubric&quot;&gt;這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;「身為一個專業的網管，有時將一個看不順眼的使用者停權也是非常合情合理的。」&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;有時我們會需要將某一個使用者停權，讓他暫時不能登入系統，
請注意！是「暫時」不能登入而已，所以不能直接刪除他的帳號，
不然之後就沒辦法回復了。&lt;/p&gt;
&lt;p&gt;那要怎麼做呢？事實上有很多種方法可以做到這一點，差別只是方法有好有壞而已，
我在這裡簡單分成三種類型的方式，分別為「針對使用者的方法」、「針對密碼的方法」和「針對 login shell 的方法」，
並稍微做一個比較。&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;section-1&quot;&gt;
&lt;h2&gt;針對使用者的方法&lt;/h2&gt;
&lt;p&gt;這種方法很簡單，就是使用 vipw 指令編輯密碼檔，
然後在該使用者那一行最前加上「#」。
比如說若我要將可惡的 marco 停權，我可以這麼做：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; sudo vipw
&lt;/pre&gt;
&lt;p&gt;然後在 marco 那一行前面加上 「#」&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
#marco:$1$65Y.0rv4$DSzwced814r9dA2ZSodLQ0:1001:1001::0:0:marco li:/home/marco:/bin/tcsh
&lt;/pre&gt;
&lt;p&gt;這個做法的意思其實就是把這一行註解掉，於是這個使用者就「不存在」了，
自然就不能登入系統，之後回復的辦法也很簡單，就是把「#」去掉即可。&lt;/p&gt;
&lt;p&gt;不過這個方法的壞處是在停權的期間，無法用「id」這個指令找到這個使用者，
而且這個使用者的檔案，上面的使用者的資訊也只剩 UID，對大部分的使用者而言，
只看 UID 這個數字並不知道到底是誰，所以會有一點不方便。&lt;/p&gt;
&lt;p&gt;另外，除了加「#」外，也可以加「-」代替，比如說&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
-marco:$1$65Y.0rv4$DSzwced814r9dA2ZSodLQ0:1001:1001::0:0:marco li:/home/marco:/bin/tcsh
&lt;/pre&gt;
&lt;p&gt;基本上結果加「-」和加「#」幾乎完全沒有區別，缺點也和上面說的一樣，
所以單看你喜歡用什麼方法，如果覺得「-」比較性感，那就用這個吧。&lt;/p&gt;
&lt;p&gt;不過由於這個方法的缺點是使用者的資訊會消失，所以一般來說並不是很推薦使用。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-2&quot;&gt;
&lt;h2&gt;針對密碼的方法&lt;/h2&gt;
&lt;p&gt;這個方法一樣是要使用「vipw」編輯密碼檔，但卻是對使用者的密碼動手腳，
不過當然不是隨便亂改別人的密碼，而是有規則的，這樣才比較容易回復。&lt;/p&gt;
&lt;p&gt;這個方法就是在使用者密碼前面加上「*」，這樣一來，因為密碼永遠都不可能對上，
自然就不能登進入。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
marco:*$1$65Y.0rv4$DSzwced814r9dA2ZSodLQ0:1001:1001::0:0:marco li:/home/marco:/bin/tcsh
&lt;/pre&gt;
&lt;p&gt;而回復也很容易，只要之後把「*」去掉即可，而且和第一種方法比起來，
使用者資訊可以保留下來，所以「id」是可以找到這個使用者的。&lt;/p&gt;
&lt;p&gt;但是加「*」也有一些小缺點，那就是「*」看起來並不顯眼，所以若直接瀏覽下來很容易就會沒注意到，
更好的方式是改換成在前面加上「*LOCKED*」。&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
marco:*LOCKED*$1$65Y.0rv4$DSzwced814r9dA2ZSodLQ0:1001:1001::0:0:marco li:/home/marco:/bin/tcsh
&lt;/pre&gt;
&lt;p&gt;這樣子一來，簡單清楚明瞭，棒極了。&lt;/p&gt;
&lt;p&gt;不過身為一個懶惰的網管，每次停權一個人，還要打這麼多字，實在有一點麻煩。
所以事實上，還有另一個更好的辦法(偷懶的辦法)，就是使用指令「pw」。&lt;/p&gt;
&lt;p&gt;比如想停權該死的 marco 這個使用者，只要打上&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; sudo pw lock marco
&lt;/pre&gt;
&lt;p&gt;就可以輕鬆的停權 marco 了，而事實上「pw」就是在做我剛剛說的事情，
就是在密碼那一欄前面加上「*LOCKED*」。&lt;/p&gt;
&lt;p&gt;而且 pw 還很好心的另外提供一個回復的指令，那就是&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; sudo pw unlock marco
&lt;/pre&gt;
&lt;p&gt;這個指令做的事情也很簡單，其實就是將「*LOCKED*」去掉而已。
所以使用「pw」來停權是一個非常方便，而且推薦的辦法。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;login-shell&quot;&gt;
&lt;h2&gt;針對 login shell 的方法&lt;/h2&gt;
&lt;p&gt;事實上，還有第三種方法也是非常推薦的，就是將使用者的 shell 改成 /sbin/nologin。
這種方法一樣可以保留使用者的資訊，而且還有前述方法沒有的好處。&lt;/p&gt;
&lt;p&gt;舉例來說，若果然還是想把萬惡的 marco 停權的話，那就可以改成：&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
marco:$1$65Y.0rv4$DSzwced814r9dA2ZSodLQ0:1001:1001::0:0:marco li:/home/marco:/sbin/nologin
&lt;/pre&gt;
&lt;p&gt;之前提到的方法，使用者都是完全登不進系統的，
所以其實沒辦法判別自己到底是帳密打錯了呢？還是被停權了？
但是若使用這個方法如果 marco 輸入正確的帳密是可以登得進系統的，只是一登入系統，
就會跳出下面訊息&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
This account is currently not available.
&lt;/pre&gt;
&lt;p&gt;然後才踢掉，所以 marco 可以很清楚的知道自己到底是帳密打錯了，還是被停權了。
但也不是說這個方法就比上一種好，因為這個方法的缺點是把人家的 login shell 改掉了，
因此回復的時候就會比較麻煩一點，所以也不是完美的方法。&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;section-3&quot;&gt;
&lt;h2&gt;結論&lt;/h2&gt;
&lt;p&gt;我認為三種方法都是好方法，網管們可以依不同的情況使用不同的方法，
所以看在我打字打得這麼辛苦的份上(還有可憐的 marco 因為測試被停權這麼多次的份上)，
還是都每個方法都用用看吧。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[不要點我]]></title>
        <id>https://blog.marco79423.net/articles/%E4%B8%8D%E8%A6%81%E9%BB%9E%E6%88%91/</id>
        <link href="https://blog.marco79423.net/articles/%E4%B8%8D%E8%A6%81%E9%BB%9E%E6%88%91/"/>
        <updated>2013-02-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[






名稱
不要點我

使用語言
Qt

使用平台
Windows

版本資訊
0.9.4

作者
大類

下載連結
載點



這是一個小遊戲 ...]]></summary>
        <content type="html"><![CDATA[&lt;img alt=&quot;donotpress.png&quot; src=&quot;https://blog.marco79423.net/backend/static/24a33040-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;18%&quot; /&gt;
&lt;col width=&quot;82%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;名稱&lt;/td&gt;
&lt;td&gt;不要點我&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用語言&lt;/td&gt;
&lt;td&gt;Qt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用平台&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;版本資訊&lt;/td&gt;
&lt;td&gt;0.9.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;作者&lt;/td&gt;
&lt;td&gt;大類&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;下載連結&lt;/td&gt;
&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://blog.marco79423.net/backend/static/24a37e7e-2cd0-11f1-bbd2-32abd871d697/&quot;&gt;載點&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;這是一個小遊戲，規則很簡單，就是點就對了。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;這個小遊戲靈感來自 Chrome 的一個擴充功能，名字叫作 DoNotPress。
當然啦，這應該不是最原始的版本，最原始的版本是什麼我也不清楚。&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;開發這個遊戲基本上沒有什麼技術難度，最難的地方在於要如何想一堆傲嬌的句子，
這對如此正經的我似乎有點強人所難......無奈。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[生命遊戲]]></title>
        <id>https://blog.marco79423.net/articles/%E7%94%9F%E5%91%BD%E9%81%8A%E6%88%B2/</id>
        <link href="https://blog.marco79423.net/articles/%E7%94%9F%E5%91%BD%E9%81%8A%E6%88%B2/"/>
        <updated>2013-02-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[






名稱
生命遊戲

使用語言
Qt

使用平台
Windows

版本資訊
0.5.4

作者
大類

下載連結
載點



簡介 ...]]></summary>
        <content type="html"><![CDATA[&lt;img alt=&quot;gameoflife.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2592eb08-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;18%&quot; /&gt;
&lt;col width=&quot;82%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;名稱&lt;/td&gt;
&lt;td&gt;生命遊戲&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用語言&lt;/td&gt;
&lt;td&gt;Qt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用平台&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;版本資訊&lt;/td&gt;
&lt;td&gt;0.5.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;作者&lt;/td&gt;
&lt;td&gt;大類&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;下載連結&lt;/td&gt;
&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://blog.marco79423.net/backend/static/259351f6-2cd0-11f1-bbd2-32abd871d697/&quot;&gt;載點&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;簡介&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;生命遊戲(Game of Life) 是一個很有意思的玩意兒，它特別的地方在於這個遊戲不需要玩家，
遊戲就會根據「規則」自動運行下去。&lt;/p&gt;
&lt;p&gt;那「規則」是什麼？&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
假設現在有一個世界，裡面有很多細胞，那麼對於現存活的細胞來說：

* 當周圍只有零個或一個細胞時，就會孤獨而死
* 若超過三個細胞時，就會資源匱乏而死
* 但其餘情況的話，就會活得很快樂

另一方面，附近有三個活細胞時，就會在中間產生一個新的細胞(人家要三個才能生......)
&lt;/pre&gt;
&lt;p&gt;我們可以用左鍵點擊遊戲畫面，該處就會出現一個小點，一個小點代表一個細胞，
接下來可以在附近多點弄多一點細胞，之後就可以開始觀察遊戲世界的變化。&lt;/p&gt;
&lt;p&gt;這個遊戲還有一個進階玩法，就是開始有顏色模式(選單左邊第三個按扭)，
然後就可以用多種不同顏色的細胞，而這個玩法的特點在於，如果如果附近那些顏色的細胞比較多，
生出該顏色的細胞機率就會比較大。&lt;/p&gt;
&lt;img alt=&quot;gameoflife-color.png&quot; src=&quot;https://blog.marco79423.net/backend/static/25931f4c-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;(選擇多種不同的顏色)&lt;/p&gt;
&lt;img alt=&quot;gameoflife-color2.png&quot; src=&quot;https://blog.marco79423.net/backend/static/2592a102-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;(然後靜靜觀察)&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;這個遊戲的玩法非常簡單，就是用滑鼠對著遊戲畫面亂點，然後一直看著發呆即可。
完全沒有上手難度，你可以一直看、一直看、一直看.....看到到天荒地老，是一個殺時間的好遊戲！&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[字型檢視器]]></title>
        <id>https://blog.marco79423.net/articles/%E5%AD%97%E5%9E%8B%E6%AA%A2%E8%A6%96%E5%99%A8/</id>
        <link href="https://blog.marco79423.net/articles/%E5%AD%97%E5%9E%8B%E6%AA%A2%E8%A6%96%E5%99%A8/"/>
        <updated>2013-02-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[






名稱
字型檢視器

使用語言
Qt

使用平台
Windows

版本資訊
0.9

作者
大類

下載連結
載點



簡介
當寫文章或設計網站的時候 ...]]></summary>
        <content type="html"><![CDATA[&lt;img alt=&quot;fontviewer.png&quot; src=&quot;https://blog.marco79423.net/backend/static/257c4dee-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;18%&quot; /&gt;
&lt;col width=&quot;82%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;名稱&lt;/td&gt;
&lt;td&gt;字型檢視器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用語言&lt;/td&gt;
&lt;td&gt;Qt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;使用平台&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;版本資訊&lt;/td&gt;
&lt;td&gt;0.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;作者&lt;/td&gt;
&lt;td&gt;大類&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;下載連結&lt;/td&gt;
&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://blog.marco79423.net/backend/static/257cd2dc-2cd0-11f1-bbd2-32abd871d697/&quot;&gt;載點&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;簡介&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;當寫文章或設計網站的時候，有些時候會覺得字型這麼多，不知道要用那一個才好，
可能是因為不知道到底系統有那些字型？或是不知道寫的這些字配上那些字型比較好看？&lt;/p&gt;
&lt;p&gt;雖然很多軟體都有提供選擇字型的功能，
甚至還能秀出一些測試的文字讓你了解這種字型顯示的感覺，
但問題是這畢竟不是你真的要拿來顯示的文字，所以還是會有所差別，
也許它對某個文字的處理你不喜歡，但偏偏那就是你要用的......。&lt;/p&gt;
&lt;p&gt;所以為了確定那一種字型是自己真的想要的，可能就要實際一個個調整字型，
看看那種字型比較漂亮.....但這麼做實在太麻煩了！所以有需求就有工具。&lt;/p&gt;
&lt;img alt=&quot;fontviewer-test.png&quot; src=&quot;https://blog.marco79423.net/backend/static/257c9aa6-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;使用這個「字型檢視器」，就可以直接顯示所有的字型，
而且還可以完全依自己的需求設定內容，這樣一來，就可以輕鬆方便的瀏覽所有字型了。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;是不是很厲害？是不是嘛？是不是嘛？快說句話，我想聽聽看讀者的聲音，
說不定我聽得到喔？ 快說你覺得超有用！&lt;/p&gt;
&lt;p&gt;介紹的很心虛，開發的時候覺得超有用，但開發完了以後，
突然發現自己超蠢的.....嗚嗚。&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;&lt;em&gt;「身為一隻沒用的廢物，寫出沒用的程式也是非常合乎邏輯的」&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[小雞跳樓梯]]></title>
        <id>https://blog.marco79423.net/articles/%E5%B0%8F%E9%9B%9E%E8%B7%B3%E6%A8%93%E6%A2%AF/</id>
        <link href="https://blog.marco79423.net/articles/%E5%B0%8F%E9%9B%9E%E8%B7%B3%E6%A8%93%E6%A2%AF/"/>
        <updated>2013-02-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[






使用語言
C++ 和 Win API

遊戲平台
Windows 7

版本資訊
0.9.0.1

下載連結
載點 ...]]></summary>
        <content type="html"><![CDATA[&lt;img alt=&quot;chicken-mainoption.png&quot; src=&quot;https://blog.marco79423.net/backend/static/262b7648-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;18%&quot; /&gt;
&lt;col width=&quot;82%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;使用語言&lt;/td&gt;
&lt;td&gt;C++ 和 Win API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;遊戲平台&lt;/td&gt;
&lt;td&gt;Windows 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;版本資訊&lt;/td&gt;
&lt;td&gt;0.9.0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;下載連結&lt;/td&gt;
&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://blog.marco79423.net/backend/static/262be060-2cd0-11f1-bbd2-32abd871d697/&quot;&gt;載點&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;遊戲簡介&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;「日前新竹某知名大學學生，因為翅膀硬了，所以跑去跳樓，測試一下自己的飛行技巧，
但是跳下樓以後才發現因為肚子餓了，忘記怎麼飛......後來卻發生令人震驚的事......結果......」&lt;/p&gt;
&lt;p&gt;驚險的遊戲旅程，由此展開！「小雞跳樓梯」是由知名的遊戲廠商 CSsula 創意工坊所製作，
之前CSsula 創意工坊曾經製作過 「冏頭蛇」、「CSRPG」 和最令人津津樂道的 「賴聖帝君」系列。
製作團隊費時三年，將再次呈現新一代經典遊戲傳說！為了增加遊戲效果，
製作團隊更是特地請來一隻小雞來代言。而這隻小雞可不僅僅是「藝雞」，她表示她也是遊戲玩家，
參與這次「小雞跳樓梯」製作的經驗非常有趣，而製作團隊的用心更是令她印象深刻。
而「小雞跳樓梯」結合動作、冒險等各項元素，配上許多有迫力的音效加持，絕對可以給玩家前所未見的全新體驗！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;遊戲節圖&lt;/strong&gt;&lt;/p&gt;
&lt;img alt=&quot;chicken-game.png&quot; src=&quot;https://blog.marco79423.net/backend/static/262baee2-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;(遊戲畫面)&lt;/p&gt;
&lt;img alt=&quot;chicken-dead.png&quot; src=&quot;https://blog.marco79423.net/backend/static/262b12e8-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;(小雞死亡畫面)&lt;/p&gt;
&lt;img alt=&quot;chicken-profile.png&quot; src=&quot;https://blog.marco79423.net/backend/static/262b4538-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;(遊戲動畫)&lt;/p&gt;
&lt;img alt=&quot;chicken-nba.png&quot; src=&quot;https://blog.marco79423.net/backend/static/262a525e-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;(關於畫面)&lt;/p&gt;
&lt;div class=&quot;line-block&quot;&gt;
&lt;div class=&quot;line&quot;&gt;&lt;em&gt;知名藝人：「這個遊戲讓我們了解到，讓自己肚子餓，不是件好事。」&lt;/em&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;em&gt;知名雜誌總編：「這是一個小雞的身體力行的體悟之旅。『 肚子餓時，不要亂飛！』 」&lt;/em&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;em&gt;交大校長：「在交大跳樓的不一定是交大學生，請記者不要亂寫！」&lt;/em&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;em&gt;教育部長：「有這樣的事？我怎麼都不知道？喂！小黃！這麼大的事，你怎麼不通報一聲？你在搞什麼呀？」&lt;/em&gt;&lt;/div&gt;
&lt;div class=&quot;line&quot;&gt;&lt;em&gt;某總統：「我用 facebook 絕對不是為了趕時髦......咦！弄錯了？」&lt;/em&gt;&lt;/div&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[SyncToy 2.1 檔案同步工具]]></title>
        <id>https://blog.marco79423.net/articles/synctoy-21-%E6%AA%94%E6%A1%88%E5%90%8C%E6%AD%A5%E5%B7%A5%E5%85%B7/</id>
        <link href="https://blog.marco79423.net/articles/synctoy-21-%E6%AA%94%E6%A1%88%E5%90%8C%E6%AD%A5%E5%B7%A5%E5%85%B7/"/>
        <updated>2013-02-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[SyncToy 是萬惡的微軟公司出的一個免錢產品，話說雖然微軟有點邪惡，
但是他也出了不少好用的免費軟體，像 SyncToy 2.1就是一個不錯的例子，
正因為如此，當室友們都逃離windows的魔爪的同時，我還緊緊抓著 window 7 不放。
這是一個好用的檔案同步工具 ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;SyncToy 是萬惡的微軟公司出的一個免錢產品，話說雖然微軟有點邪惡，
但是他也出了不少好用的免費軟體，像 SyncToy 2.1就是一個不錯的例子，
正因為如此，當室友們都逃離windows的魔爪的同時，我還緊緊抓著 window 7 不放。&lt;/p&gt;
&lt;p&gt;這是一個好用的檔案同步工具，他的使用方式相當簡單，
和微軟一貫的作風一樣，非常的親和！所以使用上應該是沒有什麼難度才對，
真要說最困難的地方，那多半就是英文介面了吧？
不過說真的，像我這種英文白痴都知道 run 是什麼意思了，
相信各位高手應該不成問題吧？&lt;/p&gt;
&lt;p&gt;很多人因為沒有備份的習慣，所以造成了很多悲劇.....&lt;/p&gt;
&lt;p&gt;「幹！我的作業不見了！」&lt;/p&gt;
&lt;p&gt;「完了！電腦打不開~我明天要教的報告~~~」&lt;/p&gt;
&lt;p&gt;「我的情書~~~」(喔~恭喜恭喜)&lt;/p&gt;
&lt;p&gt;諸如此類，如果說大家都有備份的習慣的話，至少就可以不用為硬碟壞掉而煩惱。
我只能說，硬碟也許不像你想像的那樣堅強！如果沒有本事弄磁碟陣列或是身上沒錢的話，
使用檔案同步工具將會是一個不錯的選擇！&lt;/p&gt;
&lt;p&gt;網路上有很多相關的軟體，不過也要小心其中有很多是要錢的，
當然，許多人或許有什麼秘密手段，那就不得而之了。
不過身為一個乖寶寶，走正直路線的我，
當然就會選擇簡單又實用而且又「免錢」的 SyncToy！&lt;/p&gt;
&lt;p&gt;突然覺得我好像在賣藥？&lt;/p&gt;
&lt;p&gt;「這個 SyncToy 好用！不好用不用錢！」&lt;/p&gt;
&lt;p&gt;話說本來就不用錢，好像也不用擔心這個問題。
雖然前面說了這麼多，說備份多重要，不過我想大部分的人還是不會去做的，
原因很簡單--因為怕麻煩吧？怕很花時間吧？&lt;/p&gt;
&lt;p&gt;不過其實這個問題並不是問題，因為其實檔案備份花的時間其實很短！
通常，很重要的檔案多半是文件檔，像是報告、作業等，
這種檔案同步是非常快的，以我這個普通的學生而言，一次同步大概不用一分鐘。
而這些同步只要規律幾天一次，養成習慣就好。&lt;/p&gt;
&lt;p&gt;必要的時候，你就會很高興自己有作好備份的！&lt;/p&gt;
&lt;p&gt;現在網路上有流傳的版本有2.0 和 2.1 的版本，請認明最新的！
因為 2.1 同步的速度似乎比 2.0 快很多？&lt;/p&gt;
&lt;p&gt;還有一點，就是這個軟體雖然出了有一陣子了，
但是windows 7 或 vista 也可以使用，不用擔心！
(windows 8 我就不知道了)&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[C 語言的專用書推薦 C Programming: A Modern Approach]]></title>
        <id>https://blog.marco79423.net/articles/c-%E8%AA%9E%E8%A8%80%E7%9A%84%E5%B0%88%E7%94%A8%E6%9B%B8%E6%8E%A8%E8%96%A6-c-programming-a-modern-approach/</id>
        <link href="https://blog.marco79423.net/articles/c-%E8%AA%9E%E8%A8%80%E7%9A%84%E5%B0%88%E7%94%A8%E6%9B%B8%E6%8E%A8%E8%96%A6-c-programming-a-modern-approach/"/>
        <updated>2013-02-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[老是推薦軟體，實在也沒什麼意思，畢竟這些軟體也都不是自己寫的，
他們也沒付我廣告錢，這是何苦呢？所以這次就來推薦一下書籍好了。
別人所寫的軟體再好，也不是自己寫的，用起來也不比不上自己寫的東西爽快！
但既然要自己寫，不把 C 學好怎麼成呢？所以今天就來推薦一下有關 C ...]]></summary>
        <content type="html"><![CDATA[&lt;p&gt;老是推薦軟體，實在也沒什麼意思，畢竟這些軟體也都不是自己寫的，
他們也沒付我廣告錢，這是何苦呢？所以這次就來推薦一下書籍好了。&lt;/p&gt;
&lt;p&gt;別人所寫的軟體再好，也不是自己寫的，用起來也不比不上自己寫的東西爽快！
但既然要自己寫，不把 C 學好怎麼成呢？所以今天就來推薦一下有關 C 語言的書籍吧！&lt;/p&gt;
&lt;p&gt;之前看過不少有關 C 語言的書，老實說因為自己的英文不是挺好，所以選用的書都是中文書
，前前後後大概看了五、六本書吧？倒是發現了一些問題，
一是 C 語言的中文書並沒有想像中的那麼多，有很多其實是 C 和 C++ 合併的書，
結果裡面有關 C 的內容就少了很多。&lt;/p&gt;
&lt;p&gt;更何況 C 和 C++ 的許多觀念也不太相同，
像這種合併後的書自然不會太過強調 C 與 C++ 不同點在何處，
另外有趣的是中文書的內容還真是大同小異，就連編排的順序都差別不大，
什麼之後有什麼，第幾章是什麼，下一章就會是什麼，很多都很像，
有差的大約只是作者表達能力和圖片的多寡不同罷了。&lt;/p&gt;
&lt;p&gt;比如說很多本書的第六章或第八章會是在說明「陣列」.....老實說，感覺就像有標準教材一般，
當然這樣不是不好，只是連同的，也一如標準教材的訴求一般，&lt;/p&gt;
&lt;p&gt;簡單！非常簡單！&lt;/p&gt;
&lt;p&gt;當然對於新手來說也許這樣也不錯，很多書籍也確實滿適合新手使用，
不過對於新手我個人更傾向是讓老手來帶入門，
而不是自己讀這些書，不過要找到老手肯幫忙也不是這麼容易，
更何況老手也不一定會教就是了。&lt;/p&gt;
&lt;p&gt;不過說實話，這些書都這麼厚，又這麼貴，看了這麼一大本書，裡面卻沒什麼內容，
不覺得滿空虛的嗎？這也許和人的個性有關，至少本人不太能容忍這種浪費時間的行為。&lt;/p&gt;
&lt;p&gt;沒什麼特別的意思，
但是我真的認為所謂「外國人」寫的書確實好上許多，可惜的是這些書卻不見得有中文譯本。
畢竟台灣小小的，可是用英文寫作的地方卻大大的，真的有差。&lt;/p&gt;
&lt;img alt=&quot;https://blog.marco79423.net/backend/static/25a6e072-2cd0-11f1-bbd2-32abd871d697/&quot; src=&quot;https://blog.marco79423.net/backend/static/25a6e072-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;而我要推薦的這本，書名叫「C Programming: A Modern Approach」，
書名是英文的，也許看倌心中正漸漸泛起一股莫名的害怕，
心想：「莫非這是一本原文書？」&lt;/p&gt;
&lt;p&gt;沒錯！它還真的是本原文書，你唯一可以看見中文的地方大概就是包裝吧？
不過自然要看你去那一家店買。
不過，話說身為一個痛恨英文，視中文為唯一世界語言的最高指導原則的我，
竟然還是選擇了它！究竟是為什麼！？&lt;/p&gt;
&lt;p&gt;因為，是它觸動了我敏感脆弱的心，讓這本書成為我第一本自發看完的原文書！&lt;/p&gt;
&lt;p&gt;講到這兒，我憶起當年看這本書時，眼前那遙遠而美麗的星光！&lt;/p&gt;
&lt;p&gt;講到這兒，我彷彿看見了，我當年看這本書時顫抖的手。&lt;/p&gt;
&lt;p&gt;講到這兒，我的淚水......喜悅的淚水便已滾滾落下，不能自己......&lt;/p&gt;
&lt;p&gt;總之，廢話不多說(雖然剛剛說的都是廢話)！
這本書真的很適合各個想學好 C 語言的新手，甚至老手也成。
因為除了裡面詳實的內容，和各個簡單易懂的解釋外，&lt;/p&gt;
&lt;p&gt;對於C語言的新標準(C99)和舊標準(C89 or C90)也會做詳細比較，
而且對於新標準他會額外標示，所以對不想知道 C99 內容的新手而言，
最大的麻煩就是當看見 C99 這個字樣時， 要讓眼睛從上一段直接跳到下一段，
眼球移動所花的熱量而已。&lt;/p&gt;
&lt;p&gt;而對於老手來說，也許新版標準和舊版標準差別正是可以解答自己程式碼不能 work 的好幫手。
而且雖然說得非常詳細，但也不會太大本，
每一章節都有附問答集，這大概是我認為這本書最棒的地方了，
雖然裡面有些英文對我來說頗有難度，搞得我得天天得跑去找教授解答，
教授還以為我多認真，其實我只是看不懂其中的英文罷了。&lt;/p&gt;
&lt;p&gt;話又說回來，英文像我一樣廢的大概不多，所以既然我都能看完，
裡面的英文確實是不難。啊！差點忘記介紹問答集了，我覺得這個還滿特別的，
書裡面會提到一些該章節的問題，老實說，有些問題還頗怪，
但是最後便會發現對自己的觀念真的很有幫助。&lt;/p&gt;
&lt;p&gt;推薦給大家。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[被 Qt 快樂地玩弄 - 好書推薦]]></title>
        <id>https://blog.marco79423.net/articles/%E8%A2%AB-qt-%E5%BF%AB%E6%A8%82%E5%9C%B0%E7%8E%A9%E5%BC%84-%E5%A5%BD%E6%9B%B8%E6%8E%A8%E8%96%A6/</id>
        <link href="https://blog.marco79423.net/articles/%E8%A2%AB-qt-%E5%BF%AB%E6%A8%82%E5%9C%B0%E7%8E%A9%E5%BC%84-%E5%A5%BD%E6%9B%B8%E6%8E%A8%E8%96%A6/"/>
        <updated>2013-02-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[
我想向大家推薦一本 Qt 的教學書，書名為 Foundations of Qt Development，
作者是 Johan Thelin。我不太喜歡英文，所以可能的話，我會儘量買中文的教學書，
而我幾乎買了我所有能買到的 ...]]></summary>
        <content type="html"><![CDATA[&lt;img alt=&quot;foundations-of-qt-development.png&quot; src=&quot;https://blog.marco79423.net/backend/static/26516402-2cd0-11f1-bbd2-32abd871d697/&quot; /&gt;
&lt;p&gt;我想向大家推薦一本 Qt 的教學書，書名為 Foundations of Qt Development，
作者是 Johan Thelin。我不太喜歡英文，所以可能的話，我會儘量買中文的教學書，
而我幾乎買了我所有能買到的 Qt 中文教本，繁體、簡體都有，但是我遺憾的發現，
我最喜歡的還是這本 Foundations of Qt Development 。&lt;/p&gt;
&lt;p&gt;以我閱讀的經驗，我覺得 Qt 教學書主要可以分為兩種類型，
一種就像是百科全書型，另一種則是範例集型。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;百科全書型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用這種方式的教學書，最主要的特點是會各個常用的類別一字排開，
每一個類別都會條列出各種成員函式，然後分別說明它們的用法，最後再給個範例。&lt;/p&gt;
&lt;p&gt;雖然說這種方式的好處是，如果你突然忘了某個類別的功能時，可以很迅速的查找出這個類別的用法。&lt;/p&gt;
&lt;p&gt;但這種方式卻有一個很大的問題－－非常無聊！而且就算看完了整本書，
而且書上的範例也都看得懂，但自己真的要寫 Qt 程式的時候，還是會不知道如何下手。&lt;/p&gt;
&lt;p&gt;原因很簡單，因為看作者的範例時，我們注意的是「程式的運行方式」，
卻可能看不出「為什麼要用這種運行方式？」，如果我們不了解這其中的設計哲學，
在實際寫 Qt 程式的時候，自然會覺得不知從何下手。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;範例集型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;這種教學書則剛好相反，整本書就像是範例集，從開始到結束都是在講解各種不同的範例，
如果碰到一個觀念，就會停下來解釋一下，然後再繼續講解範例。&lt;/p&gt;
&lt;p&gt;如果這時讀者真的能遵照範例一個個讀下去，相信收獲是很大的。但是這有一個前提，
那就是除非這本書你每一個範例都感興趣，都會去仔細讀它，不然只要有其中有一個範例沒興趣，
跳過不讀，你可能就會不小心錯過一些重要觀念。&lt;/p&gt;
&lt;p&gt;另外還有一個問題，那就是如果有什麼內容忘記了，你也很難從這本書找到所需要的資料。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;融合型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我覺得這兩種方式各有好壞，不過當然最好的方式就是兩種方式融會貫通，
而這本 Foundations of Qt Development ，雖然不敢說它完美，但至少我覺得融得還算不錯，
它確實包含了不少概念上的東西，也提供了不少範例給讀者練習。這本書啟發了我不少東西，
所以我非常推薦給各位閱讀。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[UNIX - ports 和 package]]></title>
        <id>https://blog.marco79423.net/articles/unix-ports-%E5%92%8C-package/</id>
        <link href="https://blog.marco79423.net/articles/unix-ports-%E5%92%8C-package/"/>
        <updated>2013-02-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。
剛灌好的 FreeBSD 裡頭什麼也沒有，什麼事都不能做，
也因此我們必須透過一些手段來安裝好用的工具來使用。就像是 Windows 灌完後，
我們可能會需利用隨身碟灌防毒軟體 ...]]></summary>
        <content type="html"><![CDATA[&lt;p class=&quot;rubric&quot;&gt;這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。&lt;/p&gt;
&lt;p&gt;剛灌好的 FreeBSD 裡頭什麼也沒有，什麼事都不能做，
也因此我們必須透過一些手段來安裝好用的工具來使用。就像是 Windows 灌完後，
我們可能會需利用隨身碟灌防毒軟體、利用光碟安裝無蝦米、或是利用 IE 灌新的瀏覽器等。
而 FreeBSD 也是一樣，除了上述的方式，FreeBSD 也提供了更好的方法來取得所需要的軟體工具。&lt;/p&gt;
&lt;p&gt;方法很簡單，就和 App Store 一樣，FreeBSD 整合了許多實用的軟體，讓我們可以統一由一個地方取得軟體，
而且這些軟體都是經過優化，適合 FreeBSD 使用，所以幾乎不會出現相容性的問題，
而最重要的是，裡面的軟體幾乎都是開源免費的！&lt;/p&gt;
&lt;p&gt;而這個地方就是 /usr/ports 這個資料夾，裡面有所有可用軟體的安裝資訊，
我們就可以利用這些安裝資訊下載並安裝我們所需要的軟體。
而這些軟體都已經分門別類好了，所以可以很輕易的找到我們要的軟體。&lt;/p&gt;
&lt;p&gt;當然，若還是找不到，我們可以在 /usr/ports 這個資料夾中輸入下面的指令搜尋&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; make search key=vim  #意思是搜尋 vim 這個軟體的位置
&lt;/pre&gt;
&lt;p&gt;找到這個所需要的軟體的目錄後，我們在這個目錄中再輸入下列指令&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; make install clean   #要有管理員權限
&lt;/pre&gt;
&lt;p&gt;就可以安裝了，安裝完就能立刻使用，非常方便。不需要自己去找軟體、不需要自己檢查相容性
不需要自己找地方下載，FreeBSD 都可以幫忙搞定整個過程。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;搜尋軟體的方法很多，不限一種，像我通常會用一個叫 psearch 的工具來搜尋&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;再來解釋一下&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; make install clean
&lt;/pre&gt;
&lt;p&gt;的運作，其實這個指令可以拆分成四個指令，我會一一解是這四個指令的意義：&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; make fetch
&amp;gt;&amp;gt;&amp;gt; make
&amp;gt;&amp;gt;&amp;gt; make install
&amp;gt;&amp;gt;&amp;gt; make clean
&lt;/pre&gt;
&lt;p&gt;首先是&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; make fetch
&lt;/pre&gt;
&lt;p&gt;這個指令的意思是根據這個資料夾(現在已經在要安裝軟體的資料夾)提供的軟體資訊，
下載軟體所需要的原始碼&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;會下載到 /usr/ports/distfiles 這個資料夾&lt;/p&gt;
&lt;/div&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; make
&lt;/pre&gt;
&lt;p&gt;然後這個指令就是編譯成執行檔&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; make install
&lt;/pre&gt;
&lt;p&gt;最後則是安裝編譯完的執行檔。由於前三個指令都是相依的，所以我們可以單純只下最後這個指令，
FreeBSD 會自動完成前面兩個指令，才開始安裝。&lt;/p&gt;
&lt;p&gt;最後是&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; make clean
&lt;/pre&gt;
&lt;p&gt;裝完了以後，由於編譯過程的檔案不需要了，為了減少浪費空間，所以這個指令就是把這些檔案清掉的意思。&lt;/p&gt;
&lt;p&gt;由這個過程，我們也可以知道 /usr/ports 裡面其實並沒有任何軟體的原始碼，只有一些軟體的訊息而已，
所以這個資料夾本身並不會佔太多空間。而所謂的更新 ports 也只是更新軟體的資訊而已，並沒有更新原始碼，
因為本來就沒有原始碼在其中。&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;更新的方式是透過 CVS 來搞定，不過之後會全改成 SVN&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;除了這種安裝方式，我們也可以直接下載安裝執行檔。相比起來，剛剛提的方法是比較好的方式，
無論是大小、效率都可能會比較好，因為如果是自行編譯的話，我們就可以自由設定我們所需要的功能，把不要的功能去掉來節省空間，
而另一方面，由於下載的安裝檔為了要相容所有機器，所以常常會在效能上做出妥協，增加一些不必要的內容。&lt;/p&gt;
&lt;p&gt;不過話說回來，前者因為要自行編譯，可能要花許多時間來完成，而後者就快得多，所以如果不介意運行的效果，
而且為了節省時間，使用後者也是不錯的選擇。&lt;/p&gt;
&lt;p&gt;這個方法可以用下面這個指令直接下載並安裝所需要的檔案。&lt;/p&gt;
&lt;pre class=&quot;doctest-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; pkg_add -r vim
&lt;/pre&gt;
&lt;p&gt;當然，不是只能用這兩種方式下載，比如說像在 Windows 上操作的那樣，
從網路上下載軟體來使用，只是這兩種方式是最方便，而且主要的下載方式而已。&lt;/p&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[UNIX - Shadow Password]]></title>
        <id>https://blog.marco79423.net/articles/unix-shadow-password/</id>
        <link href="https://blog.marco79423.net/articles/unix-shadow-password/"/>
        <updated>2013-02-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。
Shadow Password 簡單來講說就是將真正的密碼存在另一份檔案，
原來的那一份「密碼檔」裡面其實並沒有包含密碼的資訊。
比如說在 FreeBSD 的 ...]]></summary>
        <content type="html"><![CDATA[&lt;p class=&quot;rubric&quot;&gt;這裡討論的內容以 FreeBSD 為主，可能會與 linux 系的不同。&lt;/p&gt;
&lt;p&gt;Shadow Password 簡單來講說就是將真正的密碼存在另一份檔案，
原來的那一份「密碼檔」裡面其實並沒有包含密碼的資訊。&lt;/p&gt;
&lt;p&gt;比如說在 FreeBSD 的「/etc/passwd」這個檔案，裡面有各個使用者的資訊，
包含使用者名稱、家目錄、使用的 Shell等，但就是沒有包含密碼內容，
而真正的密碼是放在「/etc/master.passwd」裡，只有 root 才能查看。&lt;/p&gt;
&lt;p&gt;話又說回來，既然 root 可以查看「/etc/master.passwd」的內容，
那豈不是就知道所有人的密碼了嗎？那這樣還有誰敢登入？&lt;/p&gt;
&lt;p&gt;當然不！&lt;/p&gt;
&lt;p&gt;這裡首先要知道 FreeBSD 的密碼系統是怎麼運作的，使用者輸入密碼後，
輸入的密碼會先經過 hash 後，才比對「/etc/master.passwd」裡的值是不是相同，若是相同，
就表示輸入的密碼正確，是真正的使用者，因為密碼正確。也就是說，
「/etc/master.passwd」存放的其實也不是真正的密碼，而是 hash 過後的值。&lt;/p&gt;
&lt;p&gt;hash 指的是單向雜湊函式，這種函式的特點就是單一方向運算很簡單，
但反方向推算卻非常困難。也就是說，我可以把一個值轉換成另一個值，
但我很難把值轉換回來。而且原則上，這種函式不同值轉換的值幾乎不會重覆。&lt;/p&gt;
&lt;p&gt;這種函式非常適合用來儲存密碼，因為我們無法從 hash 過的值推算回密碼，
所以就算 root 可以看到這份密碼檔，還是不會知道實際的密碼是什麼
(雖然不知道密碼，但 root 還是可以改你的密碼，畢竟 root 是神呀~).&lt;/p&gt;
&lt;p&gt;而且不同的密碼幾乎不會產生同樣的 hash 值，
所以我們可以直接比對 hash 值是否相同來判斷使用者是不是打了正確的密碼。&lt;/p&gt;
&lt;p&gt;順帶一提，真正運作時，系統還會在使用者輸入新的密碼時，隨機產生一個數字(稱作 salt)，添加進密碼中，
所以就算是同樣的密碼，可能 hash 出來的結果也都會不同。當然這個隨機產生的數字還是要存進去才行。&lt;/p&gt;
&lt;p&gt;「/etc/master.passwd」儲存密碼的時候，共有三個欄位，由 $ 分隔，其格式如下:&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
$id$salt$encrpted
&lt;/pre&gt;
&lt;p&gt;id 指的是用什麼 hash function，若為 1 則是採用 MD5，第二個則是產生的隨機數字，最後一項才是 hash 出來的值，
詳細資訊可以查詢 crypt(3)&lt;/p&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p&gt;在「/etc/login.conf」中，可以設定要使用的 hash 函式，比如說 MD5。&lt;/p&gt;
&lt;ul class=&quot;last simple&quot;&gt;
&lt;li&gt;passwd_format md5&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;舉例來說:&lt;/p&gt;
&lt;pre class=&quot;literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt;  cat /etc/master.passwd | grep marco
marco:$1$65Y.0rv4$PLzeceW8U4rDbA2ZSorLQ0:1001:1001::0:0:marco li:/home/marco:/bin/tcsh
&lt;/pre&gt;
&lt;p&gt;「那為什麼要將密碼存在另一個檔案呢？反正我也沒辦法由 hash 猜到別的使用者的密碼呀？」&lt;/p&gt;
&lt;p&gt;理由是使用者的密碼通常不會太複雜，常常就是那幾個字，
可能性其實不夠多，所以若我可以看到密碼 hash 過後的值，
我還是有機會從 hash 猜到真正的密碼是什麼。&lt;/p&gt;
&lt;p&gt;為了減少發生問題的可能性，最實在的辦法就是讓使用者連看都看不到，
不過話說如此，也不能把「密碼檔」的讀取權限拿掉，讓使用者看不見「密碼檔」，
因為上面還包含很多資訊是要讓使用者知道的，所以就另外產生一個檔案來存放密碼，
而且這個檔案除了 root 外，其他人都看不到內容，
這種把檔案分成兩個的方式就是「Shadow Password」的機制。&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;25%&quot; /&gt;
&lt;col width=&quot;75%&quot; /&gt;
&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;/etc/passwd&lt;/td&gt;
&lt;td&gt;密碼檔，包含使用者的相關資訊&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;/etc/master.passwd&lt;/td&gt;
&lt;td&gt;真正存放密碼的檔案(不同作業系統，可能名稱會不太一樣)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;admonition note&quot;&gt;
&lt;p class=&quot;first admonition-title&quot;&gt;Note&lt;/p&gt;
&lt;p class=&quot;last&quot;&gt;為什麼要用「Shadow Password」呢？其實理由我也不清楚，
這裡寫的理由是我在網路上看到的一個說法，不知道是不是正確的，不過不重要，
重要的是理解這是在幹麻就行了。話說回來，我覺得這不是好理由，
因為一開始就不要在密碼檔放個人資訊就好了呀？也許是歷史遺毒的關係吧？。&lt;/p&gt;
&lt;/div&gt;
]]></content>
        <author>
            <name>兩大類</name>
            <email>marco79423@gmail.com</email>
            <uri>https://blog.marco79423.net</uri>
        </author>
    </entry>
</feed>