<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Wei 自己寫點什麼</title>
    <link>https://weiblog.me/</link>
    <description>攝影 x 筆記 x 隨筆</description>
    <language>en</language>
    <copyright>All rights reserved 2026, wei</copyright>
    <lastBuildDate>Fri, 12 Jun 2026 15:37:11 GMT</lastBuildDate>
    <generator>Hexo</generator>
    <atom:link href="https://weiblog.me/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>動畫 「地」 裡面最喜歡的一段對話</title>
      <link>https://weiblog.me/2026-06-08/2026-chi-anime-quotes/</link>
      <description>一起感動吧！</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/%E5%8B%95%E7%95%AB/">動畫</category>
      <category domain="https://weiblog.me/tags/%E9%96%B1%E8%AE%80/">閱讀</category>
      <category domain="https://weiblog.me/tags/%E6%80%9D%E8%80%83/">思考</category>
      <category domain="https://weiblog.me/tags/%E5%9C%B0/">地</category>
      <enclosure url="https://weiblog.me/2026-06-08/2026-chi-anime-quotes/image-0.png"/>
      <pubDate>Mon, 08 Jun 2026 14:21:23 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-06-08/2026-chi-anime-quotes/image-0.png" class=""><div style="height: 48px"></div><p>「地」是我非常喜歡的一部作品，動畫作得很不錯，OP 和 ED 也選得很好，每次聽到 「何度でも…何度でも叫ぶ…」 心裡都會回想起前一集的震撼，搭配音樂一起看下去吧！</p><div style="height: 28px"></div><iframe data-testid="embed-iframe" style="border-radius:12px" src="https://open.spotify.com/embed/track/7sMRDjjwsB7wQEBOkdfg0i?utm_source=generator" width="100%" height="352" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe><div style="height: 24px"></div><iframe data-testid="embed-iframe" style="border-radius:12px" src="https://open.spotify.com/embed/track/7ugSlmtBWNMAgTpdvBPcIh?utm_source=generator" width="100%" height="352" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe><div style="height: 56px"></div><p>作品中透過不同的角色產生很多哲學性的對話，探討不同層面的問題例如什麼是知性、什麼是信念等等。其中最讓我有共鳴的一段對話出自動畫第十七集，叔叔在河邊對杜拉卡講的話：</p><div style="height: 28px"></div><blockquote><p><strong>考えろ。そのために文字を学べ、本を読め。物知りになるためじゃないぞ、考えるためだ。</strong><br>思考。為此，要學習文字、讀書。這不是為了要博學多聞，而是為了學會思考。</p></blockquote><div style="height: 32px"></div><blockquote><p><strong>一見無関係な情報と情報の間に関わりを見つけ出せ。ただの情報を使える知識に変えるんだ。その過程に知性が宿る</strong><br>從乍看毫無關係的資訊中，找出其中的關連性，將單純的資訊轉變為可用的知識，在這些過程中，就能累積知性</p></blockquote><div style="height: 48px"></div><p>這段從一個配角口中說出來的話一字一句的打動了我，他傳遞了一個重要的觀念：</p><div style="height: 20px"></div><blockquote><p>讀書是為了讓我們思考</p></blockquote><div style="height: 32px"></div><p>我覺得他說得很好，比起博學多聞，閱讀和寫作是最能刺激思考的兩件事，這在不同的書中已經多次被不同的作者提及，特別是在連思考都能外包的 AI 世代，持續保持思考我覺得是一項需要被注意的全民運動。</p><div style="height: 32px"></div><p>雖然有點胃痛，但還是強烈推薦這部作品！</p><p>一緒に感動して！</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>從 Roo Code 到 Opencode：當 AI 工具開始不只是寫程式</title>
      <link>https://weiblog.me/2026-06-05/2026-opencode-roo-code-ai-tools-evolution/</link>
      <description>公司又換工具了...</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/AI/">AI</category>
      <category domain="https://weiblog.me/tags/AI-Coding/">AI Coding</category>
      <category domain="https://weiblog.me/tags/Opencode/">Opencode</category>
      <category domain="https://weiblog.me/tags/Roo-Code/">Roo Code</category>
      <category domain="https://weiblog.me/tags/%E9%96%8B%E7%99%BC%E5%B7%A5%E5%85%B7/">開發工具</category>
      <enclosure url="https://weiblog.me/2026-06-05/2026-opencode-roo-code-ai-tools-evolution/image-0.jpg"/>
      <pubDate>Fri, 05 Jun 2026 01:54:42 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-06-05/2026-opencode-roo-code-ai-tools-evolution/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><p>公司最近又換 AI coding tool 了，我怎麼會說又呢，因為 Opencode 太好用啦，<del>絕對不是 Roo Code Sunset 了</del>。</p><p>一開始我沒辦法理解工具一直換但最後不是都是在解決問題和產生程式碼嗎</p><p>我原本以為差異只是這樣：</p><ul><li>Opencode 是 Multi-Agent 架構</li><li>Roo Code 是 Single-Agent 架構</li><li>Opencode 是 CLI based</li><li>Roo Code 是 VS Code extension</li></ul><p>但，真的只有這樣嗎？</p><h2 id="先來回顧一下一路走來我們怎麼使用這些工具的"><a href="#先來回顧一下一路走來我們怎麼使用這些工具的" class="headerlink" title="先來回顧一下一路走來我們怎麼使用這些工具的"></a>先來回顧一下一路走來我們怎麼使用這些工具的</h2><p>把近幾年的工具發展大致分成幾個階段。</p><h3 id="Before-AI-時代"><a href="#Before-AI-時代" class="headerlink" title="Before AI 時代"></a>Before AI 時代</h3><p>代表工具：</p><ul><li>Jedi</li><li>Pylance</li><li>IntelliSense</li></ul><p>工作模式：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs text">Developer<br>    ↓<br>敲鍵盤，tab、tab、tab<br>    ↓<br>補 function name<br></code></pre></td></tr></table></figure><p>這邊只能做到基本的程式碼補齊、Go to Definition 之類現代 IDE 的基本功能。</p><h3 id="第一階段：Code-Completion"><a href="#第一階段：Code-Completion" class="headerlink" title="第一階段：Code Completion"></a>第一階段：Code Completion</h3><p>代表工具：</p><ul><li>GitHub Copilot</li><li>Tabnine</li></ul><p>工作模式：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs text">Developer<br>    ↓<br>Copilot<br>    ↓<br>補程式碼<br></code></pre></td></tr></table></figure><p>這個階段 AI 工具的工作就是根據上下文，把剩下的程式碼補齊。</p><h3 id="第二階段：Coding-Agent"><a href="#第二階段：Coding-Agent" class="headerlink" title="第二階段：Coding Agent"></a>第二階段：Coding Agent</h3><p>代表工具：</p><ul><li>Cursor</li><li>Gemini Code Assist</li><li>Roo Code</li><li>現在的 Copilot</li></ul><p>工作模式：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs text">Developer<br>    ↓<br>Roo Code<br>    ↓<br>分析專案<br>    ↓<br>修改專案<br></code></pre></td></tr></table></figure><p>這個階段的 AI 工具不只能補全程式碼，它還可以：</p><ul><li>閱讀專案</li><li>分析架構</li><li>修改多個檔案</li><li>提出建議</li><li>執行測試</li><li>修正錯誤</li></ul><p>這個時期的 AI 已經是個稱職的輔助駕駛了。</p><h3 id="第三階段：Agent-Runtime"><a href="#第三階段：Agent-Runtime" class="headerlink" title="第三階段：Agent Runtime"></a>第三階段：Agent Runtime</h3><p>代表工具：</p><ul><li>Opencode</li><li>Claude Code</li><li>Codex CLI</li><li>Pi Agent</li></ul><p>工作模式：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs text">Developer<br>    ↓<br>Opencode<br>    ↓<br>Task Decomposition<br>    ↓<br>Tool / Agent Orchestration<br>    ↓<br>Validated Output<br></code></pre></td></tr></table></figure><p>這個時候的用法已經不是只能在 VS Code 中下一句 prompt 說：「幫我實作某某功能」，而是 「某某 team 想要在 Snowflake 下開立新的 table，你幫他跑完所有流程」</p><h2 id="那-Roo-Code-和-Opencode-到底有什麼區別？"><a href="#那-Roo-Code-和-Opencode-到底有什麼區別？" class="headerlink" title="那 Roo Code 和 Opencode 到底有什麼區別？"></a>那 Roo Code 和 Opencode 到底有什麼區別？</h2><p>要我來總結的話我會說處理的問題不同，Roo Code 這種 Coding 工具是偏向給開發者使用，通常會和 IDE 整合，但 Opencode 這種 Agent 則把能處理的事情往上提升一個層級，透過 MCP 或是 A2A Opencode 能做到不只是 「Coding」 這件事。</p><p>舉例來說，我可以靠 Opencode 組織一個開發團隊，裡面包含 PM、設計師、SA、SWE、QA 等等，交辦任務後就讓這個團隊去完成工作了。</p><p>另一個例子是假設今天我是 DevOps 遇到了 GKE 網路問題，我可以同時指派 Network agent、GKE agent、GCP agent 同時下去查問題，這個就會比 Roo Code 透過一次次來回蒐集線索然後決定下一步還會快速。</p><h3 id="Multi-Agent-vs-Single-Agent-Runtime"><a href="#Multi-Agent-vs-Single-Agent-Runtime" class="headerlink" title="Multi-Agent vs Single-Agent Runtime"></a>Multi-Agent vs Single-Agent Runtime</h3><p>我們可以從 Roo Code 的原始碼來看看它是怎麼建立 subtasks 的 (下面的 Code 都有簡化過)</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NewTaskTool</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">BaseTool</span>&lt;<span class="hljs-string">&quot;new_task&quot;</span>&gt; &#123;<br><span class="hljs-keyword">async</span> <span class="hljs-title function_">execute</span>(<span class="hljs-attr">params</span>: <span class="hljs-title class_">NewTaskParams</span>, <span class="hljs-attr">task</span>: <span class="hljs-title class_">Task</span>, <span class="hljs-attr">callbacks</span>: <span class="hljs-title class_">ToolCallbacks</span>): <span class="hljs-title class_">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; &#123;<br><span class="hljs-keyword">const</span> &#123; mode, message, todos &#125; = params<br><br><span class="hljs-keyword">if</span> (!mode || !message) &#123;<br><span class="hljs-keyword">return</span><br>&#125;<br><br><span class="hljs-keyword">const</span> targetMode = <span class="hljs-title function_">getModeBySlug</span>(mode, state?.<span class="hljs-property">customModes</span>)<br><span class="hljs-keyword">if</span> (!targetMode) &#123;<br><span class="hljs-title function_">pushToolResult</span>(formatResponse.<span class="hljs-title function_">toolError</span>(<span class="hljs-string">`Invalid mode: <span class="hljs-subst">$&#123;mode&#125;</span>`</span>))<br><span class="hljs-keyword">return</span><br>&#125;<br><br><span class="hljs-keyword">const</span> toolMessage = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(&#123;<br><span class="hljs-attr">tool</span>: <span class="hljs-string">&quot;newTask&quot;</span>,<br><span class="hljs-attr">mode</span>: targetMode.<span class="hljs-property">name</span>,        <span class="hljs-comment">// ← 指定 mode</span><br><span class="hljs-attr">content</span>: message,             <span class="hljs-comment">// ← Context 內容</span><br><span class="hljs-attr">todos</span>: todoItems,             <span class="hljs-comment">// ← 初始 todo</span><br>&#125;)<br><br><span class="hljs-keyword">const</span> didApprove = <span class="hljs-keyword">await</span> <span class="hljs-title function_">askApproval</span>(<span class="hljs-string">&quot;tool&quot;</span>, toolMessage)<br><span class="hljs-keyword">if</span> (!didApprove) <span class="hljs-keyword">return</span><br><br><span class="hljs-keyword">const</span> child = <span class="hljs-keyword">await</span> provider.<span class="hljs-title function_">delegateParentAndOpenChild</span>(&#123;<br><span class="hljs-attr">parentTaskId</span>: task.<span class="hljs-property">taskId</span>,<br><span class="hljs-attr">message</span>: unescapedMessage,<br><span class="hljs-attr">initialTodos</span>: todoItems,<br>mode,<br>&#125;)<br><br><span class="hljs-title function_">pushToolResult</span>(<span class="hljs-string">`Delegated to child task <span class="hljs-subst">$&#123;child.taskId&#125;</span>`</span>)<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>可以看到 Roo Code 雖然可以作 orchestration 建立 subtask，但是它建立的是 child task，不是另一個可獨立配置的 subagent，而是把需要用到的 Mode, Todo 以及 Context 傳遞下去。</p><p>而 Opencode 是用兩步驟來建立 subagent</p><p>建立新的 session</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">const</span> nextSession =<br>  session ??<br>  (<span class="hljs-keyword">yield</span>* sessions.<span class="hljs-title function_">create</span>(&#123;<br>    <span class="hljs-attr">parentID</span>: ctx.<span class="hljs-property">sessionID</span>,<br>    <span class="hljs-attr">title</span>: params.<span class="hljs-property">description</span> + <span class="hljs-string">` (@<span class="hljs-subst">$&#123;next.name&#125;</span> subagent)`</span>,<br>    <span class="hljs-attr">agent</span>: next.<span class="hljs-property">name</span>,<br>    <span class="hljs-attr">permission</span>: [<br>      ...<span class="hljs-title function_">deriveSubagentSessionPermission</span>(&#123;<br>        <span class="hljs-attr">parentSessionPermission</span>: parent.<span class="hljs-property">permission</span> ?? [],<br>        parentAgent,<br>        <span class="hljs-attr">subagent</span>: next,<br>      &#125;),<br>      ...(cfg.<span class="hljs-property">experimental</span>?.<span class="hljs-property">primary_tools</span>?.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> (&#123;<br>        <span class="hljs-attr">pattern</span>: <span class="hljs-string">&quot;*&quot;</span>,<br>        <span class="hljs-attr">action</span>: <span class="hljs-string">&quot;allow&quot;</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,<br>        <span class="hljs-attr">permission</span>: item,<br>      &#125;)) ?? []),<br>    ],<br>  &#125;))<br></code></pre></td></tr></table></figure><p>建立 subagent</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">const</span> runTask = <span class="hljs-title class_">Effect</span>.<span class="hljs-title function_">fn</span>(<span class="hljs-string">&quot;TaskTool.runTask&quot;</span>)(<span class="hljs-keyword">function</span>* () &#123;<br>  <span class="hljs-keyword">const</span> parts = <span class="hljs-keyword">yield</span>* ops.<span class="hljs-title function_">resolvePromptParts</span>(params.<span class="hljs-property">prompt</span>)  <span class="hljs-comment">// 解析 prompt 中的參數</span><br>  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">yield</span>* ops.<span class="hljs-title function_">prompt</span>(&#123;<br>    <span class="hljs-attr">messageID</span>: <span class="hljs-title class_">MessageID</span>.<span class="hljs-title function_">ascending</span>(),<br>    <span class="hljs-attr">sessionID</span>: nextSession.<span class="hljs-property">id</span>,  <span class="hljs-comment">// ← 傳給新 subagent 的 session</span><br>    <span class="hljs-attr">model</span>: &#123;<br>      <span class="hljs-attr">modelID</span>: model.<span class="hljs-property">modelID</span>,<br>      <span class="hljs-attr">providerID</span>: model.<span class="hljs-property">providerID</span>,<br>    &#125;,<br>    <span class="hljs-attr">variant</span>: next.<span class="hljs-property">model</span> ? <span class="hljs-literal">undefined</span> : variant,<br>    <span class="hljs-attr">agent</span>: next.<span class="hljs-property">name</span>,<br>    <span class="hljs-attr">tools</span>: &#123;<br>      ...(next.<span class="hljs-property">permission</span>.<span class="hljs-title function_">some</span>(<span class="hljs-function">(<span class="hljs-params">rule</span>) =&gt;</span> rule.<span class="hljs-property">permission</span> === <span class="hljs-string">&quot;todowrite&quot;</span>) ? &#123;&#125; : &#123; <span class="hljs-attr">todowrite</span>: <span class="hljs-literal">false</span> &#125;),<br>      ...(next.<span class="hljs-property">permission</span>.<span class="hljs-title function_">some</span>(<span class="hljs-function">(<span class="hljs-params">rule</span>) =&gt;</span> rule.<span class="hljs-property">permission</span> === id) ? &#123;&#125; : &#123; <span class="hljs-attr">task</span>: <span class="hljs-literal">false</span> &#125;),<br>      ...<span class="hljs-title class_">Object</span>.<span class="hljs-title function_">fromEntries</span>((cfg.<span class="hljs-property">experimental</span>?.<span class="hljs-property">primary_tools</span> ?? []).<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> [item, <span class="hljs-literal">false</span>])),<br>    &#125;,<br>    parts,  <span class="hljs-comment">// ← 傳遞 prompt 內容（context/task）</span><br>  &#125;)<br>  <span class="hljs-keyword">return</span> result.<span class="hljs-property">parts</span>.<span class="hljs-title function_">findLast</span>(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> item.<span class="hljs-property">type</span> === <span class="hljs-string">&quot;text&quot;</span>)?.<span class="hljs-property">text</span> ?? <span class="hljs-string">&quot;&quot;</span><br>&#125;)<br></code></pre></td></tr></table></figure><p>這兩種執行方式不單單只是多工及速度上的差異，首先來談談 Roo 這樣作有什麼優缺點。</p><p>Roo 的作法是使用單一 Agent 搭配不同的 Mode 來做到角色切換。它的 runtime 比較像 task stack 模型，parent task 會暫停，child task 被 push 到 stack top 成為 active task，完成後再回到 parent task，所以一次主要只有一個 task 或 subtask 在推進。我們用起來有時候會覺得 Roo 在執行階段非常慢，但這樣的設計還是可以帶來一些好處:</p><ol><li>一次只執行一個 task，使用者可以持續關注當前的 task，隨時介入</li><li>不用處理 Race condition 問題</li><li>執行是線性的，要 debug 會比較容易</li></ol><p>另一方面 Opencode 採用 multi-agent 的設計，可以設計成多個 subagent 平行執行。除了效率外，agent 的客製化會是一個決定性的區別，在 Opencode 中我們可以自己定義 agent 的 workflow、有哪些權限以及工具可以使用，這樣使用 Opencode 這種工具能做到事情從 「hey, 幫我開發這個功能」 進化到 「hey, 幫我解決這個問題」。</p><h2 id="為什麼新一代工具開始回到-CLI？"><a href="#為什麼新一代工具開始回到-CLI？" class="headerlink" title="為什麼新一代工具開始回到 CLI？"></a>為什麼新一代工具開始回到 CLI？</h2><p>我的看法是，如同前面提到的工具進化，現在 AI 工具能做到的不再只是 Coding 而已，所以跳脫 IDE 的設計勢在必行。IDE 是程式碼的工作空間而 Terminal 是系統的工作空間，擁有系統的權限意味著 agent 能夠為你服務更多事情。</p><h2 id="Roo-與-Opencode-的核心差異"><a href="#Roo-與-Opencode-的核心差異" class="headerlink" title="Roo 與 Opencode 的核心差異"></a>Roo 與 Opencode 的核心差異</h2><p>最後總結：</p><table><thead><tr><th>Roo Code</th><th>Opencode</th></tr></thead><tbody><tr><td>IDE First</td><td>Agent First</td></tr><tr><td>Code-centric</td><td>Work-centric</td></tr><tr><td>Single active task</td><td>Agent orchestration</td></tr><tr><td>Context Continuity 強</td><td>Task Decomposition 強</td></tr><tr><td>適合個人開發</td><td>適合複雜工作流</td></tr></tbody></table>]]>
      </content:encoded>
    </item>
    <item>
      <title>【財富階梯】讀後心得</title>
      <link>https://weiblog.me/2026-06-01/2026-the-wealth-ladder-reading-notes/</link>
      <description>本文為《財富階梯》一書的讀後感</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E8%AE%80%E6%9B%B8%E5%BF%83%E5%BE%97/">讀書心得</category>
      <category domain="https://weiblog.me/tags/%E9%96%B1%E8%AE%80%E5%8A%9B/">閱讀力</category>
      <category domain="https://weiblog.me/tags/%E6%8A%95%E8%B3%87/">投資</category>
      <category domain="https://weiblog.me/tags/%E7%90%86%E8%B2%A1/">理財</category>
      <enclosure url="https://weiblog.me/2026-06-01/2026-the-wealth-ladder-reading-notes/image-0.jpg"/>
      <pubDate>Mon, 01 Jun 2026 13:59:26 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-06-01/2026-the-wealth-ladder-reading-notes/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><h2 id="關於這本書"><a href="#關於這本書" class="headerlink" title="關於這本書"></a>關於這本書</h2><p>這本書是持續買進作者尼克・馬朱利 （Nick Maggiulli）的新書，作者提出了一個很有意思的觀念「財富階梯」，讓想要增加財富但不得其法的人們可以快速了解每個階層的人物側寫、以及如何增加財富。</p><p>本書可以分成兩個段落，前面用很大篇幅解釋由統計資料分群出來的六個群組對應財富階梯的六個階層，介紹了每個階層的相異之處，以及位於不同階層要「升級」的建議。後半段則是分享一些作者對於金錢的思考，這段是我覺得比較有趣的部分。</p><h2 id="如果只能畫一條重點"><a href="#如果只能畫一條重點" class="headerlink" title="如果只能畫一條重點"></a>如果只能畫一條重點</h2><blockquote><p>鹽之於食物，猶如金錢之於人生。沒有錢，生活將淡而無味且受限，哪怕只是一點點錢，也能讓生活增添無盡樂趣。</p></blockquote><p>作者引用了美食作家莎敏納斯瑞特寫過的一段話：「鹽使食物吃起來更像它本來的味道，不加鹽，我們的食物就淡而無味，加了鹽，食物的味道就鮮活起來。」 巧妙的用鹽來比喻金錢，光有金錢並不會讓我們的生活變得更精彩，而是需要有值得灑在上面的食物。</p><p>金錢和鹽的價值，都取決於周邊的食物，不然即使爬上了財富階梯，也可能毫無意義。</p><p>這段讓我去思考了如果賺到錢了，該把錢用在什麼地方。</p><h2 id="和我的關係"><a href="#和我的關係" class="headerlink" title="和我的關係"></a>和我的關係</h2><h3 id="看見自己位於哪一階"><a href="#看見自己位於哪一階" class="headerlink" title="看見自己位於哪一階"></a>看見自己位於哪一階</h3><p>財富階梯的六個階層</p><table><thead><tr><th>階層</th><th>淨資產</th><th>階級</th><th>支出類別</th><th>最重要的事</th><th>主要資產類別</th></tr></thead><tbody><tr><td>第一階</td><td>低於 1 萬美金</td><td>底層</td><td>月光族</td><td>安全</td><td>現金、車輛</td></tr><tr><td>第二階</td><td>1 萬美金 ~ 10 萬美金</td><td>工人</td><td>雜貨店自由（生活用品價格沒這麼重要）</td><td>教育</td><td>車輛、自用住宅</td></tr><tr><td>第三階</td><td>10 萬美金 ~ 100 萬美金</td><td>中產</td><td>餐廳自由（餐廳價格沒這麼重要）</td><td>投資</td><td>自用住宅、退休金帳戶</td></tr><tr><td>第四階</td><td>100 萬美金 ~ 1000 萬美金</td><td>中上</td><td>旅行自由（旅行價格沒這麼重要）</td><td>創業</td><td>自用住宅、退休金帳戶、股票</td></tr><tr><td>第五階</td><td>1000 萬美金 ~ 1 億美金</td><td>上層</td><td>住宅自由（房子價格沒那麼重要）</td><td>擴大事業規模</td><td>事業股權、股票、退休金帳戶</td></tr><tr><td>第六階</td><td>1 億美金以上</td><td>超級富豪</td><td>影響力自由</td><td>保護財富</td><td>事業股權、股票</td></tr></tbody></table><p>我剛看到這種分階層很怕作者是要把人分等級，但這邊財富階梯的本意不是要讓人分高下，而是像是把遊戲中的戰爭迷霧打開，讓自己可以清楚看到處在哪一個位置，要前往下一個階層可以採用哪些策略。</p><p>有趣的是根據作者的觀察，相同階層的人們往往會有類似或相同的行為。我原本無法理解為什麼會有人願意花上千萬去買豪宅，但開了天眼通看到第五階才知道，原來他們是住宅自由的一群人，買龍寶和興復發對資產的影響都不大，那為什麼不買龍寶呢？ 我覺得閱讀財富階梯有趣的地方就在這。</p><h3 id="隨著財富增加而增加花費"><a href="#隨著財富增加而增加花費" class="headerlink" title="隨著財富增加而增加花費"></a>隨著財富增加而增加花費</h3><p>作者有提到一個觀念是「隨著財富增加而增加花費」，我覺得和《別把你的錢留到死》這本書的提倡的觀念有異曲同工之妙，作者提出了 0.01% 法則告訴我們隨著資產的提升，如果每次的消費都佔資產的 0.01% 你的資產不減反增，這意味著在不同階層下，你有不同的選擇自由，在這個自由下花錢可以讓生活更豐富而不是都不花錢，而這 0.01% 就可以對應到上表的「不同的自由」。</p><h3 id="金錢以外的財富"><a href="#金錢以外的財富" class="headerlink" title="金錢以外的財富"></a>金錢以外的財富</h3><p>除了講金錢財富的財富階梯外，作者還引用了創業家薩西・布魯姆的其他四種財富讓我們留意：</p><ol><li>金錢財富</li><li>心理財富</li><li>社會財富</li><li>健康財富</li><li>時間財富</li></ol><p>舉例而言，若缺乏社會財富，則需要大量其他形式的財富才能彌補，根據研究：</p><ul><li>每天幾乎都能見到朋友，相當於每年賺 10 萬美金</li><li>已婚狀態，相當於每年多賺 10 萬美金</li><li>經常與鄰居互動，價值相當於每年多賺 6 萬美金</li></ul><p>這只是其中一個例子，但作者想要傳達每種財富都很重要，而且這些財富可能唾手可得，哪怕是現在拿起電話打給許久沒碰面的老朋友，或是轉身給陪在你身邊的人一個擁抱，這些平常不會注意到的人際關係，也是我們每個人的重要資產。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Pokemon Sleep OST 大好き</title>
      <link>https://weiblog.me/2026-05-31/2026-pokemon-sleep-ost-is-awesome/</link>
      <description>Pokemon Sleep 的 BGM 怎麼這麼好聽！</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/%E9%81%8A%E6%88%B2/">遊戲</category>
      <category domain="https://weiblog.me/tags/%E9%9F%B3%E6%A8%82/">音樂</category>
      <category domain="https://weiblog.me/tags/%E7%94%9F%E6%B4%BB/">生活</category>
      <category domain="https://weiblog.me/tags/Pokemon/">Pokemon</category>
      <enclosure url="https://weiblog.me/2026-05-31/2026-pokemon-sleep-ost-is-awesome/image-0.png"/>
      <pubDate>Sun, 31 May 2026 05:59:53 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-31/2026-pokemon-sleep-ost-is-awesome/image-0.png" class=""><div style="margin-bottom: 20px"></div><p>在路邊停車讓小孩睡覺的時候，手癢想進 Pokemon Sleep 蒐集一下樹果，剛好手機因為接著 CarPlay 就把遊戲 BGM 放出來了。</p><p>今天的地圖是ウノハナ雪原，播放的 BGM 是ウノハナ雪原 (夜)，一聽驚為天人，旋律怎麼這麼好聽，因為平常玩遊戲是不開聲音的，回家馬上聽一波。</p><iframe data-testid="embed-iframe" style="border-radius:12px" src="https://open.spotify.com/embed/track/74kh8PFtSzRsIkPOigNZf0?utm_source=generator" width="100%" height="352" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe></br></br><p>這邊有個小彩蛋，如果 Spotify 介面是繁中的話可以看到一隻睡覺的皮卡丘，日文版就沒有喔！</p><img src="/2026-05-31/2026-pokemon-sleep-ost-is-awesome/image-1.png" class=""><div style="margin-bottom: 20px"></div><p>後來去查了一下 Pokemon Sleep OST 的作曲家景山将太，原本是 Game Freak 的遊戲作曲家，有許多神奇寶貝相關的作品。</p><p>最後附上 Playlist 一起來欣賞吧，這個很適合當作環境 BGM 或是睡前催眠歌曲。</p><iframe data-testid="embed-iframe" style="border-radius:12px" src="https://open.spotify.com/embed/playlist/37i9dQZF1DXa4aN4Bo2rDT?utm_source=generator" width="100%" height="352" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe><h3 id="後記"><a href="#後記" class="headerlink" title="後記"></a>後記</h3><p>最近為了製作自己想要的封面圖，學了很多 Photoshop 技巧，原來 PS 要做一個 R 角這麼複雜啊！</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>公司取消在家工作影響真的很大</title>
      <link>https://weiblog.me/2026-05-30/2026-wfh-canceled/</link>
      <description>公司取消 WFH 一定是想把大家逼走啦～</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/WFH/">WFH</category>
      <category domain="https://weiblog.me/tags/%E5%B7%A5%E4%BD%9C/">工作</category>
      <category domain="https://weiblog.me/tags/%E8%82%B2%E5%85%92/">育兒</category>
      <enclosure url="https://weiblog.me/2026-05-30/2026-wfh-canceled/image-0.png"/>
      <pubDate>Sat, 30 May 2026 07:27:20 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-30/2026-wfh-canceled/image-0.png" class=""><div style="margin-bottom: 20px"></div><p>週末在家加班有感而發，公司取消在家工作 (Working From Home, WFH) 對我的影響是非常大的，反映了也沒用，只能自己私下偷抱怨。</p><p>先來談談我在家工作享受到各種好處。</p><h3 id="絕對的專注及生產力"><a href="#絕對的專注及生產力" class="headerlink" title="絕對的專注及生產力"></a>絕對的專注及生產力</h3><p>在家工作因為不會被干擾，很容易就能進入心流，不用擔心會被同事打斷或是聽到後面有人在討論技術問題就開始側耳傾聽。</p><p>更不用說家中的設備不論是大螢幕、順手的鍵盤、「符合正常人類高度的座椅和桌子」<sup id="fnref:1" class="footnote-ref"><a href="#fn:1" rel="footnote"><span class="hint--top hint--rounded" aria-label="公司的座椅和桌面高度非常不合，桌高很高，椅子調到最高還是要聳肩才能使用鍵盤，椅子是 Enjoy 101 企業版這張椅子也很奇葩，座墊超深，扶手超高要使用扶手一定會聳肩，背要靠到底腳就會被卡住，這張椅子預設每個人身高都 180cm 是嗎？">[1]</span></a></sup> 都比公司給的破爛設備好，讓我黏在我的書桌上為公司埋頭苦幹一整天我都願意。</p><p>至少根據我的經驗，員工 WFH 常常是做到甚至忘了吃飯，最後工時還比在辦公室工作來的多。</p><h3 id="一週的家事時間"><a href="#一週的家事時間" class="headerlink" title="一週的家事時間"></a>一週的家事時間</h3><p>對於每天要花兩小時來回通勤，家裡還有小孩的家庭，別說做家事了，晚上沒有先累倒就不錯了。</p><p>在家工作就能利用休息空檔在沒有小孩的時間把衣服丟進洗衣機、簡單掃掃地、刷刷馬桶，曾經有段時間我都是用這個時間洗一週的衣服。</p><h3 id="可以接送小孩的權利"><a href="#可以接送小孩的權利" class="headerlink" title="可以接送小孩的權利"></a>可以接送小孩的權利</h3><p>因為公司的位置在一個鳥不生蛋的地方，別說要送小孩上學了，每天都必須在家人還沒起床前就必須要出門，到家的時候天都黑了。</p><p>而 WFH 的這天，我不僅可以送小孩上學，下課還能去接，除了在車上可以聊天，還能看到小孩在等候區看到你車來那雙發亮的眼睛，這是一個可以增加親子互動的好機會。</p><h3 id="省下來的通勤時間"><a href="#省下來的通勤時間" class="headerlink" title="省下來的通勤時間"></a>省下來的通勤時間</h3><p>公司福委會很常舉辦一堆體育活動鼓勵大家運動，我總是覺得非常可笑，辦這些活動不如多給幾天 WFH 讓大家省下來的時間可以自主運用。</p><p>早上一小時不用通勤我就能上語言課程，下班不用塞車，我就能出門跑步。兩個小時不斷的累積下來是很可觀的。</p><p style="text-align: center; color: #ccc; letter-spacing: 0.5em; margin: 2em 0">· · ·</p><p>當然對資方來講，不需要管員工這麼多，反正每個螺絲釘都是可以被替換的，總是會有源源不絕願意拿香蕉的猴子。另一方面，也不是所有員工都像我一樣自律而且有責任感，資方沒必要照顧少數人而承受多數人在家擺爛的風險。</p><p>先寫到這了，希望公司哪天可以想通。</p><section class="footnotes"><div class="footnote-list"><ol><li><span id="fn:1" class="footnote-text"><span>公司的座椅和桌面高度非常不合，桌高很高，椅子調到最高還是要聳肩才能使用鍵盤，椅子是 Enjoy 101 企業版這張椅子也很奇葩，座墊超深，扶手超高要使用扶手一定會聳肩，背要靠到底腳就會被卡住，這張椅子預設每個人身高都 180cm 是嗎？<a href="#fnref:1" rev="footnote" class="footnote-backref"> ↩</a></span></span></li></ol></div></section>]]>
      </content:encoded>
    </item>
    <item>
      <title>讓 WSL 再次偉大 - 一起 SSH 到 Cloud Workstation 吧</title>
      <link>https://weiblog.me/2026-05-25/wsl-ssh-cloud-workstation/</link>
      <description>記錄如何從 WSL 透過 TCP tunnel 成功 SSH 連入 GCP Cloud Workstation。</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/GCP/">GCP</category>
      <category domain="https://weiblog.me/tags/Cloud-Workstation/">Cloud Workstation</category>
      <category domain="https://weiblog.me/tags/WSL/">WSL</category>
      <category domain="https://weiblog.me/tags/SSH/">SSH</category>
      <category domain="https://weiblog.me/tags/DevTools/">DevTools</category>
      <enclosure url="https://weiblog.me/2026-05-25/wsl-ssh-cloud-workstation/image-0.png"/>
      <pubDate>Mon, 25 May 2026 13:19:09 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-25/wsl-ssh-cloud-workstation/image-0.png" class=""><div style="margin-bottom: 20px"></div><p>因為公司政策改變，現在鼓勵 developer 用 GCP Cloud Workstations 取代 VM，開發上還是習慣用 Terminal 搭配 VS Code 來作業，沒辦法從 WSL 連上 Workstation 就像是心中有個結打不開那樣（以前有成功搞定 tunnel 過，但是 IT 不知道搞了什麼，後來不能用，也沒再花時間搞了）。</p><p>最近 Roo Code sunset，迎來 opencode 的時代，這種 CLI 工具沒有 Terminal 一定痛苦死，只好又跳下來弄一弄。</p><h2 id="建立-TCP-Tunnel"><a href="#建立-TCP-Tunnel" class="headerlink" title="建立 TCP Tunnel"></a>建立 TCP Tunnel</h2><p>要用 SSH 連到 Cloud Workstation 需要透過 tunnel 來達成，可以使用這樣的指令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs shell">gcloud workstations start-tcp-tunnel \<br>  --project=my-gcp-project \<br>  --cluster=asia-southeast1-cluster \<br>  --config=vscode-small-standard \<br>  --region=asia-southeast1 \<br>  my-ws-name 22 \<br>  --local-host-port=:2222<br></code></pre></td></tr></table></figure><p>這會開啟一條 TCP tunnel，將 host 的 2222 port 轉發到 Cloud Workstation 上的 22 port。</p><p>只要再</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">ssh user@localhost -p 2222<br></code></pre></td></tr></table></figure><p>就可以透過 SSH 連進去了，這樣做還有一個好處是，可以用自己 host 的 VS Code 走 remote 的方式進到 Cloud Workstation 開發，這樣就可以使用自己 VS Code 的設定，以及 extensions。</p><p>如果覺得上面兩個步驟太麻煩，只是想要快速 SSH 進去操作，可以使用：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs shell">gcloud workstations ssh \<br>  --project=my-gcp-project \<br>  --cluster=asia-southeast1-cluster \<br>  --config=vscode-small-standard \<br>  --region=asia-southeast1 \<br>  --local-host-port=:2222 \<br>  my-ws-name<br></code></pre></td></tr></table></figure><h2 id="繞過公司-Proxy"><a href="#繞過公司-Proxy" class="headerlink" title="繞過公司 Proxy"></a>繞過公司 Proxy</h2><p>理論上上面應該都要能動，但是公司的 proxy 會擋長連線，所以 tunnel 必須繞過 proxy。本來以為把環境變數 <code>$HTTP_PROXY</code> 和 <code>$HTTPS_PROXY</code> unset 就沒事了，但是試了很多次還是得到 502。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell">unset HTTP_PROXY<br>unset HTTPS_PROXY<br>unset http_proxy<br>unset https_proxy<br></code></pre></td></tr></table></figure><p>後來查到，原來 Cloud SDK 也可以設定 Proxy。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs shell">gcloud config list --format=&quot;yaml(proxy)&quot;<br><br>proxy:<br>   address: mycompany.proxy.com<br>   port: &quot;80&quot;<br></code></pre></td></tr></table></figure><p>把這個 proxy 關了就能成功建立 tunnel 了！</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs shell">gcloud config unset proxy/address<br>gcloud config unset proxy/port<br>gcloud config unset proxy/type<br></code></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>第一次用 Pi Agent 就踩雷</title>
      <link>https://weiblog.me/2026-05-24/pi-agent-pitfall/</link>
      <description>第一次用 Pi Agent 自動化 Obsidian 筆記整理，意外觸發無限遞迴，兩小時燒光五小時 token 用量</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/AI/">AI</category>
      <category domain="https://weiblog.me/tags/Pi-Agent/">Pi Agent</category>
      <category domain="https://weiblog.me/tags/%E8%B8%A9%E9%9B%B7/">踩雷</category>
      <enclosure url="https://weiblog.me/2026-05-24/pi-agent-pitfall/image-0.png"/>
      <pubDate>Sun, 24 May 2026 13:11:26 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-24/pi-agent-pitfall/image-0.png" class=""><div style="margin-bottom: 20px"></div><p>這篇文章會紀錄我踩雷以及除錯的過程，如果想直接看結論可以直接跳至<a href="#%E7%B5%90%E8%AB%96">結論</a></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近看到很多人在推廣 <a href="https://pi.dev/">Pi Coding Agent</a> 趁著週末來玩玩看，在沒有任何背景知識下嘗試來維護我的 Obsidian 的筆記 Index，由於 Index 之前是交給 Claude Code 整理過一次，用 Pi 來測試應該不會出太大問題，但意外總是來的又急又快。</p><p>想說應該是不會很難的 Code 就不看直接測試了，結果發現 Error Log 開始狂噴，電腦逐漸卡頓，最後還收到 5 小時 Token 用量到達 limit 的通知，到底發生什麼事？</p><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>我的 Obsidian vault 有一個 <code>_metadata</code> 資料夾，裡面放了所有文章的索引、分類以及每日報告。我想讓這些資料自動更新，不用每次手動叫 agent 跑。</p><p>我讓 Pi 幫我設計這個自動化流程。它的方案如下：</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs nix">obsidian-notes<span class="hljs-symbol">/</span><br>├── .pi<span class="hljs-symbol">/</span><br>│   └── extensions<span class="hljs-symbol">/</span><br>│       └── metadata-sync.ts<br>└── scripts<span class="hljs-symbol">/</span><br>    ├── metadata_maintainer.sh<br>    └── metadata_prompt.md<br></code></pre></td></tr></table></figure><p><strong><code>metadata_maintainer.sh</code></strong> — 排程 script，給 launchd 呼叫，負責啟動 agent 執行任務：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-keyword">if</span> ! (<br>  <span class="hljs-built_in">cd</span> <span class="hljs-string">&quot;<span class="hljs-variable">$WORKSPACE</span>&quot;</span><br>  pi -p <span class="hljs-string">&quot;<span class="hljs-variable">$prompt</span>&quot;</span>   <span class="hljs-comment"># 啟動 pi agent 執行整理任務</span><br>) &gt;<span class="hljs-string">&quot;<span class="hljs-variable">$log_file</span>&quot;</span> 2&gt;&amp;1; <span class="hljs-keyword">then</span><br>  status=<span class="hljs-string">&quot;failed&quot;</span><br><span class="hljs-keyword">fi</span><br></code></pre></td></tr></table></figure><p><strong><code>metadata-sync.ts</code></strong> — Pi 的擴充功能 (extension)，讓 Pi 每次啟動時自動執行 script：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs typescript">pi.<span class="hljs-title function_">on</span>(<span class="hljs-string">&quot;session_start&quot;</span>, <span class="hljs-title function_">async</span> (event, ctx) =&gt; &#123;<br>  <span class="hljs-keyword">if</span> (event.<span class="hljs-property">reason</span> !== <span class="hljs-string">&quot;startup&quot;</span>) <span class="hljs-keyword">return</span>;<br><br>  <span class="hljs-keyword">const</span> shouldRun = ctx.<span class="hljs-property">hasUI</span><br>    ? <span class="hljs-keyword">await</span> ctx.<span class="hljs-property">ui</span>.<span class="hljs-title function_">confirm</span>(<span class="hljs-string">&quot;更新 _metadata&quot;</span>, <span class="hljs-string">&quot;要在進入 workspace 時先同步嗎？&quot;</span>)<br>    : <span class="hljs-literal">true</span>;  <span class="hljs-comment">// 非互動模式直接執行</span><br><br>  <span class="hljs-keyword">if</span> (!shouldRun) <span class="hljs-keyword">return</span>;<br>  <span class="hljs-keyword">await</span> <span class="hljs-title function_">runSync</span>(pi);  <span class="hljs-comment">// 執行 metadata_maintainer.sh</span><br>&#125;);<br></code></pre></td></tr></table></figure><p><strong><code>metadata_prompt.md</code></strong> — 告訴 Agent 要執行 task 的細節</p><p>這邊的設計是：每次用 CLI 的 Pi 打開 workspace 時，先問你要不要同步；若是自動排程（非互動）就直接執行。</p><h2 id="時間線"><a href="#時間線" class="headerlink" title="時間線"></a>時間線</h2><table><thead><tr><th>時間</th><th>事件</th></tr></thead><tbody><tr><td>10:21</td><td>Pi 寫好 <code>metadata_maintainer.sh</code> 與 <code>metadata-sync.ts</code>，user reload</td></tr><tr><td>10:40</td><td>手動跑了一次 <code>metadata_maintainer.sh</code> 測試</td></tr><tr><td>10:40:16</td><td>第一個遞迴 session 產生，爆炸開始</td></tr><tr><td>10:41–11:00</td><td>冒出 951 個 process</td></tr><tr><td>11:00</td><td>打到 Codex API rate limit，報錯 “fetch failed”</td></tr><tr><td>13:23</td><td>打到 Codex plus plan 用量上限，報錯 “usage limit”</td></tr><tr><td>下午</td><td>覺得電腦怪怪，然後怎麼會一下就用光 Token!</td></tr></tbody></table><h2 id="開始追查"><a href="#開始追查" class="headerlink" title="開始追查"></a>開始追查</h2><p>因為 Codex token 燒光了，只好回頭用 Claude Code 一起看。</p><p>先看 <code>_metadata/logs</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ <span class="hljs-built_in">ls</span> _metadata/logs/ | <span class="hljs-built_in">wc</span> -l<br>2716<br></code></pre></td></tr></table></figure><p>我的天啊，我沒寫 retry 機制，這個 script 怎麼會跑了兩千多次？！</p><p>接著看一下 Process</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ pgrep -f metadata_maintainer | <span class="hljs-built_in">wc</span> -l<br>743<br></code></pre></td></tr></table></figure><p>???</p><p>原本以為 Pi 都幫我把程式註冊到 launchd 裡面，但是查了之後也沒有，事情更怪了到底怎麼會有殺不完的 Process ?!</p><p>好在萬能的 Pi 有提供好用的 Session Log!</p><p><code>pi</code> 會在 <code>~/.pi/agent/sessions/</code> 下為每個 workspace 保存完整的對話與 tool call 記錄。我打開爆炸時的 session 目錄看到：</p><figure class="highlight subunit"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs subunit">2026<span class="hljs-string">-05</span><span class="hljs-string">-24</span>T02<span class="hljs-string">-40</span><span class="hljs-string">-13</span><span class="hljs-string">-328</span>Z_...jsonl<br>2026<span class="hljs-string">-05</span><span class="hljs-string">-24</span>T02<span class="hljs-string">-40</span><span class="hljs-string">-16</span><span class="hljs-string">-978</span>Z_...jsonl<br>2026<span class="hljs-string">-05</span><span class="hljs-string">-24</span>T02<span class="hljs-string">-40</span><span class="hljs-string">-17</span><span class="hljs-string">-536</span>Z_...jsonl<br>2026<span class="hljs-string">-05</span><span class="hljs-string">-24</span>T02<span class="hljs-string">-40</span><span class="hljs-string">-18</span><span class="hljs-string">-097</span>Z_...jsonl<br>...<br></code></pre></td></tr></table></figure><p>每個 session 的開頭都是同一段 prompt——來自 <code>metadata_prompt.md</code>，也就是 <code>metadata_maintainer.sh</code> 用 <code>pi -p &quot;$prompt&quot;</code> 傳進去的，這代表總共啟動了上面這麼多個 Pi agent。</p><p>正常的執行流程應該是這樣：</p><div style="display:flex;flex-direction:column;align-items:center;gap:0;font-size:0.95em;margin:1.5em 0;width:320px;margin-left:auto;margin-right:auto;">  <div style="background:#f0f4ff;border:1px solid #c7d4f7;border-radius:8px;padding:10px 20px;width:100%;text-align:center;box-sizing:border-box;position:relative;"><span style="position:absolute;left:16px;top:50%;transform:translateY(-50%);color:#8fa8e8;font-weight:bold;">1</span>我啟動 Pi workspace</div>  <div style="color:#999;font-size:1.2em;line-height:1.2;">↓</div>  <div style="background:#f0f4ff;border:1px solid #c7d4f7;border-radius:8px;padding:10px 20px;width:100%;text-align:center;box-sizing:border-box;position:relative;"><span style="position:absolute;left:16px;top:50%;transform:translateY(-50%);color:#8fa8e8;font-weight:bold;">2</span><code>metadata-sync.ts</code> 被呼叫</div>  <div style="color:#999;font-size:1.2em;line-height:1.2;">↓</div>  <div style="background:#f0f4ff;border:1px solid #c7d4f7;border-radius:8px;padding:10px 20px;width:100%;text-align:center;box-sizing:border-box;position:relative;"><span style="position:absolute;left:16px;top:50%;transform:translateY(-50%);color:#8fa8e8;font-weight:bold;">3</span>執行 <code>metadata_maintainer.sh</code></div>  <div style="color:#999;font-size:1.2em;line-height:1.2;">↓</div>  <div style="background:#f0f4ff;border:1px solid #c7d4f7;border-radius:8px;padding:10px 20px;width:100%;text-align:center;box-sizing:border-box;position:relative;"><span style="position:absolute;left:16px;top:50%;transform:translateY(-50%);color:#8fa8e8;font-weight:bold;">4</span>啟動 Pi agent 去執行 prompt 指令</div>  <div style="color:#999;font-size:1.2em;line-height:1.2;">↓</div>  <div style="background:#e8f5e9;border:1px solid #a5d6a7;border-radius:8px;padding:10px 20px;width:100%;text-align:center;box-sizing:border-box;position:relative;"><span style="position:absolute;left:16px;top:50%;transform:translateY(-50%);color:#6abf69;font-weight:bold;">5</span>結束</div></div><p>最多應該也只有五筆 log，但顯然事實不是這樣，而且後面很多 log 都有 prompt 指令，那就可以合理推測第四步時，Pi agent 跑起來的時候又觸發了 2~4 的過程導致一直有 Pi agent 被開起來 （是不是很像 n+1 query）</p><p>簡單的時間軸就是這樣</p><figure class="highlight julia"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs julia"><span class="hljs-literal">pi</span> 啟動，被問是否更新 metadata? -&gt; Yes<br>└→ metadata_maintainer.sh<br>   └→ <span class="hljs-literal">pi</span> -p <span class="hljs-string">&quot;<span class="hljs-variable">$prompt</span>&quot;</span>          <span class="hljs-comment"># 啟動 pi</span><br>       └→ 載入 metadata-sync.ts 擴充功能<br>               └→ session_start<br>                   └→ 執行 metadata_maintainer.sh<br>                       └→ <span class="hljs-literal">pi</span> -p <span class="hljs-string">&quot;<span class="hljs-variable">$prompt</span>&quot;</span><br>                           └→ ...<br></code></pre></td></tr></table></figure><p>就這樣直接把我的 Codex token 瞬間燒乾了…</p><p>要終止這個無限迴圈很簡單，</p><ol><li>在 <code>metadata_maintainer.sh</code> 前面加 <code>exit 0</code>，不要跑到 Pi 指令就退出</li><li>反覆 <code>pkill -9 -f metadata_maintainer</code> 殺光所有的 process（因為 detached，需要跑好幾輪）</li></ol><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><p><strong>Root Cause</strong>：Pi 在同一次對話裡做了兩件會互相觸發的事，但它沒有辦法在設計階段模擬兩者同時存在時的執行結果，需要被 review 或是其他機制來發現它的設計有問題。</p><p>清掉 process 就結束了。在 reload 或執行之前，還是需要把 agent 寫的東西讀一遍或是交給其他 agent 審查。</p><p><strong>Lessons Learned</strong>: 如果有寫 extension 且 hook 在 Pi agent 啟動時，就要特別注意遞迴呼叫的問題。</p><p>事後想想，其實把 workspace 拆開就能避免這個問題。排程用的 Pi agent 跑在一個沒有掛 extension 的獨立 workspace，它就只是執行任務的 worker，不會觸發任何自動化邏輯。</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs nix">obsidian-notes<span class="hljs-symbol">/</span>             <span class="hljs-comment"># workspace 1</span><br>└── .pi<span class="hljs-operator">/</span>extensions<span class="hljs-symbol">/</span><br>    └── metadata-sync.ts<br>obsidian-notes-scheduler<span class="hljs-symbol">/</span>   <span class="hljs-comment"># workspace 2</span><br>└── scripts<span class="hljs-symbol">/</span><br>    └── metadata-maintainer.sh<br><br></code></pre></td></tr></table></figure><p>一時想到的解法，也許社群會有其他更好的方法。</p><p>這次還好打到五小時 limit 就停了，如果是用 credit 的後果則不堪設想，沒有想到 YOLO 又翻車。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>改變人生觀的一本書</title>
      <link>https://weiblog.me/2026-05-21/life-changing-book/</link>
      <description>Blog Blog 同樂會五月主題是改變人生觀的一句話</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/Blog-Blog-%E5%90%8C%E6%A8%82%E6%9C%83/">Blog Blog 同樂會</category>
      <enclosure url="https://weiblog.me/2026-05-21/life-changing-book/image-0.jpg"/>
      <pubDate>Thu, 21 May 2026 14:24:02 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-21/life-changing-book/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><h3 id="Blog-Blog-同樂會-五月的主題是-「改變人生觀的一句話」，由-Eddie-主持。"><a href="#Blog-Blog-同樂會-五月的主題是-「改變人生觀的一句話」，由-Eddie-主持。" class="headerlink" title="Blog Blog 同樂會 五月的主題是 「改變人生觀的一句話」，由 Eddie 主持。"></a><a href="https://blogblog.club/party/">Blog Blog 同樂會</a> 五月的主題是 <a href="https://eddielv.com/articles/a-sentence-changing-you/">「改變人生觀的一句話」</a>，由 <a href="https://eddielv.com/">Eddie</a> 主持。</h3><p>我認為我的人生觀包含了大部分的工作觀，畢竟現在每天花了大把的時間在工作上。</p><p>工作上一直面臨焦慮、自我懷疑，曾經以為作為 SWE<sup id="fnref:1" class="footnote-ref"><a href="#fn:1" rel="footnote"><span class="hint--top hint--rounded" aria-label="Software Engineer，軟體工程師。">[1]</span></a></sup> 沒有能力進到 Big Tech<sup id="fnref:2" class="footnote-ref"><a href="#fn:2" rel="footnote"><span class="hint--top hint--rounded" aria-label="指 Google、Meta、Amazon、Apple、Microsoft 等大型科技公司。">[2]</span></a></sup> 就是魯蛇，至少到最近我都是這樣看自己的。</p><p>我做過自己喜歡但是錢很少的工作；也做過錢還不錯但內容很無聊的。但想想錢變多就表示責任變多，有壓力的情況下再喜歡的工作也會成為負擔吧？</p><p style="text-align: center; color: #ccc; letter-spacing: 0.5em">· · ·</p><p>如此這般地思考著工作的意義，也和很多朋友、前輩討論過。</p><p>有人認為工作就是賺錢，我出賣我的八小時給公司，這段時間就是不抱怨努力做，下班就是自己的時間別煩我。</p><p>有人認為錢拿少一點沒關係，但是工作要有意義，要能讓人產生使命感。</p><p>也有人認為工作就是工作最好錢多事少能躺平最好。</p><p style="text-align: center; color: #ccc; letter-spacing: 0.5em">· · ·</p><p>一直無法得出答案的我，在閱讀完《無路之路》這本書後覺得有點方向了。作者在一場大病後體會到把整個人生建立在工作之上，並非這麼理所當然。</p><p>書中也提到我們對工作的崇拜來自於宗教、來自於社會、來自於人。我覺察到自己在潛移默化中會特別注重工作，甚至把工作和地位畫上等號了，對別人的好奇心止於職業而不是這個人的其他面向。</p><p>既然我是這樣看別人的，代表我也會這樣看自己：</p><br><p><b>自己沒有像樣的工作 &#x3D; 魯蛇</b></p><br><p>William J. Reilly 在《不要工作的方法》中有一段話：你的人生也太寶貴，不該浪費在工作上</p><p>這段話道出了很多 FIRE 族<sup id="fnref:3" class="footnote-ref"><a href="#fn:3" rel="footnote"><span class="hint--top hint--rounded" aria-label="Financial Independence, Retire Early，財務獨立、提早退休。">[3]</span></a></sup> 的心情，對我來說也是一記當頭棒喝，原本我還在想要做到六十歲退休，但想到我會失去什麼後我的人生觀就被改變了。</p><p>保羅在《無路之路》中提到，有一群人也是正在走在這條若有似無的路上，尋找自己的志願，他們離開自己的舒適圈不斷的探索，就算還沒找到答案，過程中也會滿載而歸。</p><blockquote><p><b>尋找值得做的工作，就是我們真正的工作</b></p></blockquote><p>這讓我有了脫離舒適圈的勇氣，我決定要持續尋找自己真正想做的事。</p><p style="text-align: center; color: #ccc; letter-spacing: 0.5em">· · ·</p><p>現在的我不會再用公司或是 Title 來定義自己或別人，也認為工作只是一種手段不是使命，我想最好的策略就是一邊累積資產一邊尋找自己可以一直投入的事情，當條件滿足後，就能退休了。</p><p>我一直認為退休不是整天無所事事，而是指可以安心做自己想做的事情而不用擔心生存的那種狀態。</p><p>最後附上一句：</p><blockquote><p><b>Find a job you enjoy doing, and you will never have to work a day in your life.</b><sup id="fnref:4" class="footnote-ref"><a href="#fn:4" rel="footnote"><span class="hint--top hint--rounded" aria-label="此句常被引用為 Mark Twain 所說，但出處有爭議，目前無法確認原始來源。">[4]</span></a></sup></p></blockquote><br><hr><section class="footnotes"><div class="footnote-list"><ol><li><span id="fn:1" class="footnote-text"><span>Software Engineer，軟體工程師。<a href="#fnref:1" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:2" class="footnote-text"><span>指 Google、Meta、Amazon、Apple、Microsoft 等大型科技公司。<a href="#fnref:2" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:3" class="footnote-text"><span>Financial Independence, Retire Early，財務獨立、提早退休。<a href="#fnref:3" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:4" class="footnote-text"><span>此句常被引用為 Mark Twain 所說，但出處有爭議，目前無法確認原始來源。<a href="#fnref:4" rev="footnote" class="footnote-backref"> ↩</a></span></span></li></ol></div></section>]]>
      </content:encoded>
    </item>
    <item>
      <title>台中人的第一次吃忠孝路夜市</title>
      <link>https://weiblog.me/2026-05-18/taichung-person-first-time-zhongxiao-night-market/</link>
      <description>為什麼在台中這麼久都沒來過呢？</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/%E5%8F%B0%E4%B8%AD/">台中</category>
      <category domain="https://weiblog.me/tags/%E5%A4%9C%E5%B8%82/">夜市</category>
      <category domain="https://weiblog.me/tags/%E7%BE%8E%E9%A3%9F/">美食</category>
      <enclosure url="https://weiblog.me/2026-05-18/taichung-person-first-time-zhongxiao-night-market/image-0.jpg"/>
      <pubDate>Mon, 18 May 2026 15:17:14 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-18/taichung-person-first-time-zhongxiao-night-market/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><br><p>某個晚上，對就是把小孩送回娘家，我們夫妻倆人獲得短暫空檔組裝樂高的那個晚上。</p><p>決定去忠孝路夜市找找美食，說來慚愧，從小到大都沒有來過忠孝路夜市，學生時期都在一中街打轉，偶爾跑去逢甲這樣，但從沒想過要來忠孝路夜市，這是為什麼呢？</p><p>ぜんぜん分かんない！</p><p>也許只是沒想到吧！</p><p>總而言之稍微做了一下功課後兩個人就騎著機車興沖沖前往了。</p><p>騎到附近看到到處都是車，心裡暗道不妙，不趕快找車位停後面就會找不到車位。</p><p>後來才知道忠孝路夜市正確的吃法是騎機車直接騎到你要的攤位或店面前，沒有人像我們鄉巴佬一樣以為這裡是徒步區。</p><p>下面是這次踩點吃的：</p><h3 id="熊家湯包"><a href="#熊家湯包" class="headerlink" title="熊家湯包"></a>熊家湯包</h3><p>本來兩個人只想吃一籠湯包，留著胃去吃其他美食的，結果看到菜單胃口大開就爆點了一波，原以為鮮蝦腐皮捲是港點那種小小條，結果我們都傻眼，一盤有三條超大蝦捲就這樣端上來。</p><p>剝皮辣椒湯包非常好吃，皮很薄湯汁超多，就像其他網友說的一樣湯匙裝不下流出來的湯汁，配上薑絲可以一口接一口吃光光。</p><p>小缺點是店內沒有提供小碗，只能用湯匙辛苦的吃湯包，還要小心不能讓湯汁流出來。</p><h3 id="忘了名字的地瓜球"><a href="#忘了名字的地瓜球" class="headerlink" title="忘了名字的地瓜球"></a>忘了名字的地瓜球</h3><p>台中人不說 QQ 蛋，請正名「地瓜球」！</p><p>走了很久都沒看到有傳統那種小小顆，類似逢甲大門口那家的地瓜球，最後隨便買，是大顆的也算很好吃。</p><h3 id="炎伯紅茶-（阿義紅茶冰）"><a href="#炎伯紅茶-（阿義紅茶冰）" class="headerlink" title="炎伯紅茶 （阿義紅茶冰）"></a>炎伯紅茶 （阿義紅茶冰）</h3><p>就在熊家湯包對面，看到很多人在排隊跟風喝一下，回家才知道原本叫阿義紅茶冰，但紅茶冰我真的喝不出差別</p><ul><li>阿月紅茶冰</li><li>老賴紅茶冰</li><li>炎伯紅茶冰</li></ul><p>我喝起來都一樣，不要爆甜我就給過。</p><h3 id="南海茶道"><a href="#南海茶道" class="headerlink" title="南海茶道"></a>南海茶道</h3><p>以前聽同事推薦過，看到想點現泡茶，一問才知道一杯要等 20 ~ 30 分鐘果斷放棄換一般茶。太太還說他們辦公室很常點，而且他都點現泡茶（店員應該很想翻白眼）</p><h3 id="阿緣香雞排"><a href="#阿緣香雞排" class="headerlink" title="阿緣香雞排"></a>阿緣香雞排</h3><p>來自一位資深忠孝路夜市專家推薦，號稱地頭最好吃鹹酥雞，必點雞皮然後要馬上吃掉。</p><p>本來是想照著上面攻略吃的，但前面實在吃太飽連偷吃的胃口都沒了…</p><figure style="text-align: center; margin-bottom: 10px; width: 80%; margin-left: auto; margin-right: auto">  <img src="/2026-05-18/taichung-person-first-time-zhongxiao-night-market/image-1.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">好吃，而且份量很多，據說以前沒有號碼牌現場是一片混亂呢</figcaption></figure><p>其實應該還有很多好吃的，但是年紀到了，食量不爭氣害我不能好好享用阿，下次再來吃點不一樣的。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>原來組裝樂高是這麼療癒的嗎？ 開箱北齋名作</title>
      <link>https://weiblog.me/2026-05-15/lego-great-wave/</link>
      <description>一把年紀了玩樂高還是這麼快樂</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E7%94%9F%E6%B4%BB/">生活</category>
      <category domain="https://weiblog.me/tags/%E6%A8%82%E9%AB%98/">樂高</category>
      <category domain="https://weiblog.me/tags/%E9%96%8B%E7%AE%B1/">開箱</category>
      <enclosure url="https://weiblog.me/2026-05-15/lego-great-wave/image-0.png"/>
      <pubDate>Fri, 15 May 2026 13:21:05 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-15/lego-great-wave/image-0.png" class=""><div style="margin-bottom: 20px"></div><figure style="text-align: center; margin-bottom: 10px; width: 80%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/lego-great-wave-1.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">盒子超大，上面寫著 1810 片挺嚇人的</figcaption></figure><br><p>在 2025 年趁著特價買回來準備放在玄關，但是一直找不到時間來組裝。</p><p>趁著某個週末小孩回阿公阿嬤家，配著宵夜和飲料和太太卯起來組，本以為幾個小時可以搞定，信誓旦旦的發下豪語沒組完不睡覺，但…</p><hr><p>在開始之前，「神奈川沖浪裏」這句到底該怎麼斷詞？</p><div style="margin: 2rem 0;">  <div style="display: flex; flex-direction: column; gap: 0.75rem; max-width: 320px;">    <button onclick="handleAnswer(this, false)" style="padding: 0.75rem 1.25rem; border: 2px solid #ccc; border-radius: 8px; background: white; font-size: 1rem; cursor: pointer; text-align: left; transition: all 0.2s;">神奈川沖浪．裏</button>    <button onclick="handleAnswer(this, true)" style="padding: 0.75rem 1.25rem; border: 2px solid #ccc; border-radius: 8px; background: white; font-size: 1rem; cursor: pointer; text-align: left; transition: all 0.2s;">神奈川沖．浪裏</button>    <button onclick="handleAnswer(this, false)" style="padding: 0.75rem 1.25rem; border: 2px solid #ccc; border-radius: 8px; background: white; font-size: 1rem; cursor: pointer; text-align: left; transition: all 0.2s;">神奈川．沖浪．裏</button>  </div>  <p id="quiz-result" style="margin-top: 1rem; font-weight: bold; min-height: 1.5rem;"></p></div><style>@keyframes shake {  0%, 100% { transform: translateX(0); }  20% { transform: translateX(-6px); }  40% { transform: translateX(6px); }  60% { transform: translateX(-4px); }  80% { transform: translateX(4px); }}</style><script>function handleAnswer(btn, correct) {  var buttons = btn.parentElement.querySelectorAll('button');  buttons.forEach(function(b) { b.disabled = true; });  var result = document.getElementById('quiz-result');  if (correct) {    btn.style.borderColor = '#22c55e';    btn.style.background = '#f0fdf4';    btn.style.color = '#15803d';    result.style.color = '#15803d';    result.textContent = '✓ 正確！浪裏是「波浪之中」的意思。';  } else {    btn.style.borderColor = '#ef4444';    btn.style.background = '#fef2f2';    btn.style.color = '#b91c1c';    btn.style.animation = 'shake 0.4s ease';    result.style.color = '#b91c1c';    result.textContent = '✗ 再猜猜看？';    setTimeout(function() {      btn.style.animation = '';      buttons.forEach(function(b) { b.disabled = false; });    }, 600);  }}</script><p>神奈川沖（かながわおき）是地名，指神奈川的外海；浪裏（なみうら）是指波浪之中。合在一起就是「神奈川外海的巨浪之中」。</p><p>我一直都很喜歡北齋的浮世繪，特別是這幅以及凱風快晴，在家裡想擺一幅但是沒有空間豪邁地掛上一大幅，最後折衷買樂高 （？）。</p><hr><div style="text-align: center;">葛飾北齋</div><figure style="text-align: center; margin-bottom: 10px; width: 50%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/katsushikahokusai.webp" class=""></figure><div style="text-align: center;">Sorry 不是這位</div><figure style="text-align: center; margin-bottom: 10px; width: 50%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/katsushikahokusai-2.png" class=""></figure><div style="text-align: center;">北齋本人</div><br><p>葛飾北齋（1760–1849），江戶時代的浮世繪大師。他最狂的地方是，一生換了30幾個名號、搬了90幾次家，然後在70歲才畫出富嶽三十六景，神奈川沖浪裏就是其中最有名的那張，這是告訴我們退休還是要持續創作嗎？</p><hr><figure style="text-align: center; margin-bottom: 10px; width: 80%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/lego-great-wave-2.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">    全部倒出來有 15 包，附上一本非常精美的說明書。  </figcaption></figure><figure style="text-align: center; margin-bottom: 10px; width: 80%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/lego-great-wave-3.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">    從小單位開始組起，說明書都寫的很清楚要開哪一包，拼哪一個位置  </figcaption></figure><figure style="text-align: center; margin-bottom: 10px; width: 80%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/lego-great-wave-4.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">    以為組的有模有樣了，往旁邊望去居然還有好幾包  </figcaption></figure><figure style="text-align: center; margin-bottom: 10px; width: 80%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/lego-great-wave-5.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">    以為本體拼完就沒事了嗎？  </figcaption></figure><div style="text-align: center; font-size: 20px"> No None ない!</div><div style="text-align: center;"> 組框也是超花時間的，這時候時間已經來到十一點多，兩個人都快睡著了</div><figure style="text-align: center; margin-bottom: 10px; width: 80%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/lego-great-wave-6.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">    準備把框和畫合體，背後的卡榫設計得很有趣，框可以把畫牢牢咬住  </figcaption></figure><figure style="text-align: center; margin-bottom: 10px; width: 80%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/lego-great-wave-7.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">    できた！  </figcaption></figure><figure style="text-align: center; margin-bottom: 10px; width: 80%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/lego-great-wave-9.jpg" class=""></figure><div style="text-align: center;">細節表現的配件有滿有趣的，浪花是用一堆白色小鳥</div><figure style="text-align: center; margin-bottom: 10px; width: 80%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/lego-great-wave-10.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">    這些小人臉超級嘲諷  </figcaption></figure><figure style="text-align: center; margin-bottom: 10px; width: 80%; margin-left: auto; margin-right: auto">  <img src="/2026-05-15/lego-great-wave/lego-great-wave-8.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">    現在變成玄關裝飾了  </figcaption></figure><p>一開始看到 1800 多片想說怎麼可能拼的完，後來才知道連那種一個點的也算一片，和之前拼 F1 有些組合需要想像一下不同，只要不眼殘拼起來是很快的。雖然說很快，但從八點開始也是到十二點才完成。</p><p>一邊看著說明書組裝，背後放著輕快的 LoFi 很容易就進入心流狀態，一轉眼時間就晃過去了，等到有睡意了才發現原來已經這麼晚了，最後看到成品非常有成就感。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>為什麼我寫部落格</title>
      <link>https://weiblog.me/2026-05-13/blogging-thoughts/</link>
      <description>同樣的內容，寫在部落格上和發臉書的成就感為什麼不一樣？</description>
      <author>wei</author>
      <category domain="https://weiblog.me/tags/%E9%83%A8%E8%90%BD%E6%A0%BC/">部落格</category>
      <category domain="https://weiblog.me/tags/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <enclosure url="https://weiblog.me/2026-05-13/blogging-thoughts/image-0.jpg"/>
      <pubDate>Wed, 13 May 2026 13:06:06 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-13/blogging-thoughts/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><p>最近突然思考起這樣一個問題：</p><blockquote><p>為什麼同樣發一篇文章，發在臉書上跟寫在自己的部落格上，成就感、滿足感完全不同呢？</p></blockquote><p>畢竟部落格這種東西可能沒什麼人會看到對吧?</p><h2 id="儀式感"><a href="#儀式感" class="headerlink" title="儀式感"></a>儀式感</h2><p>在臉書或 Instagram 這些社群平台發文很快。寫完、送出，結束。</p><p>寫部落格不一樣。要挑封面圖片、裁切、壓縮、處理浮水印，寫完後還要佈署。</p><p>這個流程雖麻煩，但麻煩本身就是意義的一部分。像是在告訴自己：這篇文章是我花時間產出的。</p><h2 id="自己的空間"><a href="#自己的空間" class="headerlink" title="自己的空間"></a>自己的空間</h2><p>社群平台的 UI 不是我們決定的，而部落格是自己的地方。想改什麼就改什麼，想加上某個 feature 就叫 AI 刻出來。</p><p>主題如果看膩了還可以隨時切換，最重要的是每篇文章都有一份備份在自己的硬碟中。</p><h2 id="沒有回饋機制"><a href="#沒有回饋機制" class="headerlink" title="沒有回饋機制"></a>沒有回饋機制</h2><p>社群平台強調互動，所以會有很多回饋機制像是留言按讚分享等等。<br>在臉書寫東西結果發現沒幾個讚或是有人互動真的會非常失落 （就算寫的東西很廢也來吐槽我一下啊）。<br>而寫部落格沒有這個壓力。有人來讀，很開心；沒人看也不會患得患失。</p><hr><p>持續有在看一些日文部落格，發現很多日本人把它當雜誌在經營，從封面到排版都看得出用心，讓讀者享受瀏覽的過程。<br>現在還在練習攝影、排版，希望也能把部落格當成雜誌在經營。</p><p>從 2025 年寫下第一篇文章到現在，每次完成一篇，成就感都還是如第一次一樣。</p><p>推薦大家擁有一個專屬自己的小天地。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>用 FizzBuzz 練習 Red Green Refactor</title>
      <link>https://weiblog.me/2026-05-12/practice-rdr-with-fizzbuzz/</link>
      <description>用 FizzBuzz 練習 TDD 的 Red-Green-Refactor 流程，體驗先寫測試、再寫程式的節奏。</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/Python/">Python</category>
      <category domain="https://weiblog.me/tags/TDD/">TDD</category>
      <enclosure url="https://weiblog.me/2026-05-12/practice-rdr-with-fizzbuzz/image-0.jpg"/>
      <pubDate>Tue, 12 May 2026 14:29:22 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-12/practice-rdr-with-fizzbuzz/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><p>記得我在第一次寫 Leetcode 的時候，寫完馬上按下 Submit，然後看哪個測試沒過就改到讓他過。</p><p>後來才知道這個感覺很接近測試驅動開發(Test-Driven Development，TDD) 的精神，差別是 TDD 是你自己先寫測試，不是等別人的測試來告訴你哪裡錯了。</p><p>TDD 的宗旨就是:</p><blockquote><p><strong>先寫測試，再寫程式。</strong></p></blockquote><p>有別於傳統先開發再測試的流程，TDD 有以下好處:</p><ol><li>測試本身就是一種規格，撰寫的過程可以對規格有更深入的理解</li><li>如果規格有問題或是不明確，可以在寫測試的時候就發現</li><li>開發的過程就一邊在重構了，不用等到最後一刻才發現不敢改</li></ol><p>而 Red-Green-Refactor 是 TDD 中一個重要的觀念。<br>透過 <code>寫測試 -&gt; fail (Red) -&gt; 寫 Code (Green) -&gt; 重構</code> 的迭代流程讓程式碼變完整。</p><h2 id="這個練習只需要兩個檔案"><a href="#這個練習只需要兩個檔案" class="headerlink" title="這個練習只需要兩個檔案"></a>這個練習只需要兩個檔案</h2><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs stylus">fizzbuzz-kata/<br>├── fizzbuzz<span class="hljs-selector-class">.py</span>          ← 主程式<br>└── test_fizzbuzz<span class="hljs-selector-class">.py</span>     ← 測試<br></code></pre></td></tr></table></figure><h2 id="Red-Green-Refactor-三步驟"><a href="#Red-Green-Refactor-三步驟" class="headerlink" title="Red-Green-Refactor 三步驟"></a>Red-Green-Refactor 三步驟</h2><table><thead><tr><th>步驟</th><th>檔案</th><th>做什麼</th></tr></thead><tbody><tr><td>🔴 Red</td><td><code>test_fizzbuzz.py</code></td><td>新增一個會<strong>失敗</strong>的測試，描述需求</td></tr><tr><td>🟢 Green</td><td><code>fizzbuzz.py</code></td><td>用<strong>最少</strong>的程式碼通過測試</td></tr><tr><td>🔵 Refactor</td><td><code>fizzbuzz.py</code></td><td>整理程式碼，確認測試還是綠的</td></tr></tbody></table><h2 id="FizzBuzz-Spec"><a href="#FizzBuzz-Spec" class="headerlink" title="FizzBuzz Spec"></a>FizzBuzz Spec</h2><p>在寫測試之前，必須先了解需求：</p><table><thead><tr><th>輸入</th><th>條件</th><th>輸出</th></tr></thead><tbody><tr><td>任意數字</td><td>一般情況</td><td>數字字串，例如 <code>1</code> → <code>&quot;1&quot;</code></td></tr><tr><td>3 的倍數</td><td><code>n % 3 == 0</code></td><td><code>&quot;Fizz&quot;</code></td></tr><tr><td>5 的倍數</td><td><code>n % 5 == 0</code></td><td><code>&quot;Buzz&quot;</code></td></tr><tr><td>15 的倍數</td><td><code>n % 15 == 0</code></td><td><code>&quot;FizzBuzz&quot;</code></td></tr></tbody></table><p>⚠️ TDD 之前就需要了解規格，才能把所有情況和邊界都納入測試。</p><h2 id="TDD-練習過程"><a href="#TDD-練習過程" class="headerlink" title="TDD 練習過程"></a>TDD 練習過程</h2><h3 id="Cycle-1：fizzbuzz-1-→-1"><a href="#Cycle-1：fizzbuzz-1-→-1" class="headerlink" title="Cycle 1：fizzbuzz(1) → &quot;1&quot;"></a>Cycle 1：<code>fizzbuzz(1)</code> → <code>&quot;1&quot;</code></h3><h4 id="🔴-Red-—-加測試"><a href="#🔴-Red-—-加測試" class="headerlink" title="🔴 Red — 加測試"></a>🔴 Red — 加測試</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test_fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_1_returns_1</span>():<br>    <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">1</span>) == <span class="hljs-string">&quot;1&quot;</span><br></code></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">fizzbuzz</span>(<span class="hljs-params">n</span>):<br>    <span class="hljs-keyword">pass</span><br></code></pre></td></tr></table></figure><p>執行結果（失敗）：</p><figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs fsharp">AssertionError<span class="hljs-operator">:</span> <span class="hljs-keyword">assert</span> <span class="hljs-literal">None</span> <span class="hljs-operator">==</span> <span class="hljs-string">&#x27;1&#x27;</span><br></code></pre></td></tr></table></figure><h4 id="🟢-Green-—-寫最少程式碼"><a href="#🟢-Green-—-寫最少程式碼" class="headerlink" title="🟢 Green — 寫最少程式碼"></a>🟢 Green — 寫最少程式碼</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">fizzbuzz</span>(<span class="hljs-params">n</span>):<br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;1&quot;</span><br></code></pre></td></tr></table></figure><p>執行結果：</p><figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">1 </span>passed<br></code></pre></td></tr></table></figure><h4 id="🔵-Refactor"><a href="#🔵-Refactor" class="headerlink" title="🔵 Refactor"></a>🔵 Refactor</h4><p>是的你沒看錯，寫這樣也算通過測試可以進到下一個 cycle</p><hr><h3 id="Cycle-2：fizzbuzz-2-→-2"><a href="#Cycle-2：fizzbuzz-2-→-2" class="headerlink" title="Cycle 2：fizzbuzz(2) → &quot;2&quot;"></a>Cycle 2：<code>fizzbuzz(2)</code> → <code>&quot;2&quot;</code></h3><h4 id="🔴-Red-—-加測試-1"><a href="#🔴-Red-—-加測試-1" class="headerlink" title="🔴 Red — 加測試"></a>🔴 Red — 加測試</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test_fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_2_returns_2</span>():<br>    <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">2</span>) == <span class="hljs-string">&quot;2&quot;</span><br></code></pre></td></tr></table></figure><p>執行結果（失敗）：</p><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs avrasm"><span class="hljs-symbol">AssertionError:</span> assert <span class="hljs-string">&#x27;1&#x27;</span> == <span class="hljs-string">&#x27;2&#x27;</span><br></code></pre></td></tr></table></figure><h4 id="🟢-Green-—-寫最少程式碼-1"><a href="#🟢-Green-—-寫最少程式碼-1" class="headerlink" title="🟢 Green — 寫最少程式碼"></a>🟢 Green — 寫最少程式碼</h4><p><code>return &quot;1&quot;</code> 只能處理 <code>1</code>，改成通用的字串轉換：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">fizzbuzz</span>(<span class="hljs-params">n</span>):<br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(n)<br></code></pre></td></tr></table></figure><p>執行結果：</p><figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">2 </span>passed<br></code></pre></td></tr></table></figure><h4 id="🔵-Refactor-1"><a href="#🔵-Refactor-1" class="headerlink" title="🔵 Refactor"></a>🔵 Refactor</h4><p>只有一行不太需要重構</p><hr><h3 id="Cycle-3：fizzbuzz-3-→-Fizz"><a href="#Cycle-3：fizzbuzz-3-→-Fizz" class="headerlink" title="Cycle 3：fizzbuzz(3) → &quot;Fizz&quot;"></a>Cycle 3：<code>fizzbuzz(3)</code> → <code>&quot;Fizz&quot;</code></h3><h4 id="🔴-Red-—-加測試-2"><a href="#🔴-Red-—-加測試-2" class="headerlink" title="🔴 Red — 加測試"></a>🔴 Red — 加測試</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test_fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_3_returns_fizz</span>():<br>    <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">3</span>) == <span class="hljs-string">&quot;Fizz&quot;</span><br></code></pre></td></tr></table></figure><p>執行結果（失敗）：</p><figure class="highlight ceylon"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ceylon">AssertionError: <span class="hljs-keyword">assert</span> <span class="hljs-string">&#x27;3&#x27;</span> == <span class="hljs-string">&#x27;Fizz&#x27;</span><br></code></pre></td></tr></table></figure><h4 id="🟢-Green-—-加入-3-的倍數判斷"><a href="#🟢-Green-—-加入-3-的倍數判斷" class="headerlink" title="🟢 Green — 加入 3 的倍數判斷"></a>🟢 Green — 加入 3 的倍數判斷</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">fizzbuzz</span>(<span class="hljs-params">n</span>):<br>    <span class="hljs-keyword">if</span> n % <span class="hljs-number">3</span> == <span class="hljs-number">0</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;Fizz&#x27;</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(n)<br></code></pre></td></tr></table></figure><p>執行結果：</p><figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">3 </span>passed<br></code></pre></td></tr></table></figure><h4 id="🔵-Refactor-2"><a href="#🔵-Refactor-2" class="headerlink" title="🔵 Refactor"></a>🔵 Refactor</h4><p>不用重構</p><hr><h3 id="Cycle-4：fizzbuzz-5-→-Buzz"><a href="#Cycle-4：fizzbuzz-5-→-Buzz" class="headerlink" title="Cycle 4：fizzbuzz(5) → &quot;Buzz&quot;"></a>Cycle 4：<code>fizzbuzz(5)</code> → <code>&quot;Buzz&quot;</code></h3><h4 id="🔴-Red-—-加測試-3"><a href="#🔴-Red-—-加測試-3" class="headerlink" title="🔴 Red — 加測試"></a>🔴 Red — 加測試</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test_fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_5_returns_buzz</span>():<br>    <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">5</span>) == <span class="hljs-string">&quot;Buzz&quot;</span><br></code></pre></td></tr></table></figure><p>執行結果（失敗）：</p><figure class="highlight ceylon"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ceylon">AssertionError: <span class="hljs-keyword">assert</span> <span class="hljs-string">&#x27;5&#x27;</span> == <span class="hljs-string">&#x27;Buzz&#x27;</span><br></code></pre></td></tr></table></figure><h4 id="🟢-Green-—-加入-5-的倍數判斷"><a href="#🟢-Green-—-加入-5-的倍數判斷" class="headerlink" title="🟢 Green — 加入 5 的倍數判斷"></a>🟢 Green — 加入 5 的倍數判斷</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">fizzbuzz</span>(<span class="hljs-params">n</span>):<br>    <span class="hljs-keyword">if</span> n % <span class="hljs-number">3</span> == <span class="hljs-number">0</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;Fizz&#x27;</span><br>    <span class="hljs-keyword">elif</span> n % <span class="hljs-number">5</span> == <span class="hljs-number">0</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;Buzz&#x27;</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(n)<br></code></pre></td></tr></table></figure><p>執行結果：</p><figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">4 </span>passed<br></code></pre></td></tr></table></figure><h4 id="🔵-Refactor-3"><a href="#🔵-Refactor-3" class="headerlink" title="🔵 Refactor"></a>🔵 Refactor</h4><p>不用重構</p><hr><h3 id="Cycle-5：fizzbuzz-15-→-FizzBuzz"><a href="#Cycle-5：fizzbuzz-15-→-FizzBuzz" class="headerlink" title="Cycle 5：fizzbuzz(15) → &quot;FizzBuzz&quot;"></a>Cycle 5：<code>fizzbuzz(15)</code> → <code>&quot;FizzBuzz&quot;</code></h3><h4 id="🔴-Red-—-加測試-4"><a href="#🔴-Red-—-加測試-4" class="headerlink" title="🔴 Red — 加測試"></a>🔴 Red — 加測試</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test_fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_15_returns_fizzbuzz</span>():<br>    <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">15</span>) == <span class="hljs-string">&quot;FizzBuzz&quot;</span><br></code></pre></td></tr></table></figure><p>執行結果（失敗）：</p><figure class="highlight ceylon"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ceylon">AssertionError: <span class="hljs-keyword">assert</span> <span class="hljs-string">&#x27;Fizz&#x27;</span> == <span class="hljs-string">&#x27;FizzBuzz&#x27;</span><br></code></pre></td></tr></table></figure><h4 id="🟢-Green"><a href="#🟢-Green" class="headerlink" title="🟢 Green"></a>🟢 Green</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">fizzbuzz</span>(<span class="hljs-params">n</span>):<br>    <span class="hljs-keyword">if</span> n % <span class="hljs-number">3</span> == <span class="hljs-number">0</span> <span class="hljs-keyword">and</span> n % <span class="hljs-number">5</span> == <span class="hljs-number">0</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;FizzBuzz&#x27;</span><br>    <span class="hljs-keyword">elif</span> n % <span class="hljs-number">3</span> == <span class="hljs-number">0</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;Fizz&#x27;</span><br>    <span class="hljs-keyword">elif</span> n % <span class="hljs-number">5</span> == <span class="hljs-number">0</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;Buzz&#x27;</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(n)<br></code></pre></td></tr></table></figure><p>執行結果：</p><figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">5 </span>passed<br></code></pre></td></tr></table></figure><h4 id="🔵-Refactor-4"><a href="#🔵-Refactor-4" class="headerlink" title="🔵 Refactor"></a>🔵 Refactor</h4><p>這邊可以思考兩種寫法</p><p><code>n % 3 == 0 and n % 5 == 0</code> 或是 <code>n % 15 == 0</code></p><ul><li><code>n % 3 == 0 and n % 5 == 0</code>：意圖更清楚（同時是 3 和 5 的倍數）</li><li><code>n % 15 == 0</code>：更簡潔</li></ul><p>如果效能不會差很多的話通常我都是選易讀的選項</p><hr><h3 id="Cycle-6、7、8：驗證通用邏輯"><a href="#Cycle-6、7、8：驗證通用邏輯" class="headerlink" title="Cycle 6、7、8：驗證通用邏輯"></a>Cycle 6、7、8：驗證通用邏輯</h3><p>加入更多測試案例，確認邏輯真的通用：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test_fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_6_returns_fizz</span>():<br>    <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">6</span>) == <span class="hljs-string">&quot;Fizz&quot;</span><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_10_returns_buzz</span>():<br>    <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">10</span>) == <span class="hljs-string">&quot;Buzz&quot;</span><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_30_returns_fizzbuzz</span>():<br>    <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">30</span>) == <span class="hljs-string">&quot;FizzBuzz&quot;</span><br></code></pre></td></tr></table></figure><p>執行結果：</p><figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">8 </span>passed<br></code></pre></td></tr></table></figure><hr><h2 id="Final-Code"><a href="#Final-Code" class="headerlink" title="Final Code"></a>Final Code</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># fizzbuzz.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">fizzbuzz</span>(<span class="hljs-params">n</span>):<br>    <span class="hljs-keyword">if</span> n % <span class="hljs-number">3</span> == <span class="hljs-number">0</span> <span class="hljs-keyword">and</span> n % <span class="hljs-number">5</span> == <span class="hljs-number">0</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;FizzBuzz&#x27;</span><br>    <span class="hljs-keyword">elif</span> n % <span class="hljs-number">3</span> == <span class="hljs-number">0</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;Fizz&#x27;</span><br>    <span class="hljs-keyword">elif</span> n % <span class="hljs-number">5</span> == <span class="hljs-number">0</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;Buzz&#x27;</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(n)<br></code></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test_fizzbuzz.py</span><br><span class="hljs-keyword">from</span> fizzbuzz <span class="hljs-keyword">import</span> fizzbuzz<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">TestFizzBuzz</span>:<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_1_returns_1</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">1</span>) == <span class="hljs-string">&quot;1&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_2_returns_2</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">2</span>) == <span class="hljs-string">&quot;2&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_3_returns_fizz</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">3</span>) == <span class="hljs-string">&quot;Fizz&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_5_returns_buzz</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">5</span>) == <span class="hljs-string">&quot;Buzz&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_15_returns_fizzbuzz</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">15</span>) == <span class="hljs-string">&quot;FizzBuzz&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_6_returns_fizz</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">6</span>) == <span class="hljs-string">&quot;Fizz&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_10_returns_buzz</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">10</span>) == <span class="hljs-string">&quot;Buzz&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_30_returns_fizzbuzz</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-keyword">assert</span> fizzbuzz(<span class="hljs-number">30</span>) == <span class="hljs-string">&quot;FizzBuzz&quot;</span><br></code></pre></td></tr></table></figure><hr><h2 id="心得"><a href="#心得" class="headerlink" title="心得"></a>心得</h2><p>雖然之前就有讀過類似概念，但也只是看過去而已，這次搭配 AI 教練自己下來動手作很有感。</p><ol><li>原來真的可以為了讓綠燈通過，寫出 <code>return &quot;1&quot;</code> 這種程式碼（雖然很快就會在下一輪被改掉）。</li><li>做起來真的和傳統 SDLC 不同，我的感覺是在寫測試階段，就會逼自己去了解規格，並思考各種邊界。</li><li>不確定這樣的開發方式會不會比傳統快，傳統的開發方式是大腦連貫的把已知邏輯順暢的寫完但不會管測試，而 TDD 則是片段式的慢慢把功能補齊，可以有預感 TDD 不會 overengineering。</li><li>拿 Leetcode 的題目練習感覺是個不錯的主意。</li></ol><p>思考：</p><ol><li>為什麼要用 Red-Green-Refactor 來執行 TDD，一次把所有想到的測試都寫完然後開發不好嗎？</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>【一如既往】讀後心得</title>
      <link>https://weiblog.me/2026-05-11/the-same-as-ever-reading-notes/</link>
      <description>本文為《一如既往》一書的讀後感</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E8%AE%80%E6%9B%B8%E5%BF%83%E5%BE%97/">讀書心得</category>
      <category domain="https://weiblog.me/tags/%E9%96%B1%E8%AE%80%E5%8A%9B/">閱讀力</category>
      <category domain="https://weiblog.me/tags/%E6%8A%95%E8%B3%87/">投資</category>
      <category domain="https://weiblog.me/tags/%E5%BF%83%E7%90%86%E5%AD%B8/">心理學</category>
      <enclosure url="https://weiblog.me/2026-05-11/the-same-as-ever-reading-notes/image-0.jpg"/>
      <pubDate>Mon, 11 May 2026 13:58:19 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-11/the-same-as-ever-reading-notes/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><h2 id="關於這本書"><a href="#關於這本書" class="headerlink" title="關於這本書"></a>關於這本書</h2><p>Morgan Housel 是《致富心態》的作者，在《一如既往》中他提出一個核心問題：在這個快速變化的世界裡，什麼事情是永遠不變的？</p><p>書中用了二十多個小故事傳達各種不變的事物，有些故事我看了很有感，有些可以理解但沒有共鳴，建議可以隨心所欲閱讀，有些章節只是現在道行不夠無法理解，過陣子回頭再讀便是。</p><h2 id="如果只能畫一條重點"><a href="#如果只能畫一條重點" class="headerlink" title="如果只能畫一條重點"></a>如果只能畫一條重點</h2><blockquote><p>從來不曾改變的行為是歷史最有力的教訓，因為它們預告未來的景象。</p></blockquote><p>我認為這就是本書的精華所在，世間萬物的變化是很難預測的，但是考慮到「人性」一切都變得清楚起來。</p><p>人們會被貪婪迷惑、被恐懼支配，會低估風險、學過數學但不相信數學。</p><p>作者提到要預測未來，就要從人的行為下手，當你發現這一切都不是巧合，你會笑著說：「對，一如既往」。</p><p>歷史總是一再重演，一如既往。</p><h2 id="和我的關係"><a href="#和我的關係" class="headerlink" title="和我的關係"></a>和我的關係</h2><p>我最有感觸的一章是「期望與現實」這個章節，在讀這章時處於一個極度羨慕身邊的人，覺得只有自己是收入最低的那個人，總是焦慮又悶悶不樂。</p><p>但作者在書中提到幾個重點：</p><blockquote><p>你的幸福取決於你的期望，在這個大多數時候對大部分來人來說持續變得更好的世界裡，如何不讓期望值跟著上升，就是一項重要的生活技巧。</p></blockquote><blockquote><p>如果你只想要幸福，很容易就能實現；可是我們總想要比別人更幸福，這件事就永遠很難達成，因為我們會高估別人的幸福。</p></blockquote><blockquote><p>人們會根據周遭人的狀況來衡量自己的幸福感。</p></blockquote><p>換句話說，幸福感來自於知足，知足可以讓自己的期望不會超過現實太多。期望來自於和身邊的人做比較，但我們無法知道別人實際上的情況，因此很容易被自己幻想出來的狀況影響我們的期望。</p><p>無路之路這本書有一句話我也很喜歡：<strong>「足夠就是知道衣服、大餐或是最新的玩意兒不會讓我更快樂，但偶爾買買，也不至於會破產」</strong>。這句話非常貼切的描述了庶民如我期望，想買很多喜歡的衣服、想蒐集各種鏡頭，但我知道這些事物帶來的多巴胺來的快去的也快，但偶爾買買也不失為一種樂趣。</p><p>用這些文字勉勵自己持續學習知道自己的足夠，才能找到幸福。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>二十年後の教室で</title>
      <link>https://weiblog.me/2026-05-10/back-to-high-school/</link>
      <description>回到母校，和學弟妹聊升學、聊職涯</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E7%94%9F%E6%B4%BB/">生活</category>
      <category domain="https://weiblog.me/tags/%E7%94%9F%E6%B4%BB%E7%B4%80%E9%8C%84/">生活紀錄</category>
      <enclosure url="https://weiblog.me/2026-05-10/back-to-high-school/image-0.jpg"/>
      <pubDate>Sun, 10 May 2026 01:03:11 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-10/back-to-high-school/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><p>受老師邀請和一位高中朋友一起回學校和現在的學弟妹分享、聊聊工作。</p><p>在事前準備有先發了 Google Form 調查一下學弟妹對什麼樣的問題有興趣，也針對各個問題寫下了自己的想法。</p><p>但是在當天排練時雖然有小抄還是覺得腦袋轉不過來，一直以來我的報告方式都是準備好 slide 後在每頁註解的地方標示關鍵字，剩下靠即興發揮，但這次似乎踢到鐵板了。</p><p>手上只有小抄和有 slide 的報告感覺完全不一樣，而且我們是兩人互動的方式，當下其實很難掌握說話的節奏以及內容範圍，導致雖然順利講完，但是有些我覺得很重要的事情沒有好好傳達到。</p><hr><p>有位同學下課後來問我問題，提到如果都讓 AI 寫扣了，那工程師要做什麼呢？</p><p>這個問題其實我也問過自己很多次了，當下就把我現在的答案告訴他，也許之後還會持續改變。</p><p>我是這樣回答的：「當 coding 這些事情交給 AI 後，每個人都可以成為資深工程師了，因為省去了敲鍵盤的時間，可以花更多的時間去設計系統、確認規格，過去被 deadline 追著跑而捨棄的東西，有了 AI 現在都可以做到」</p><hr><p>另一位同學的問題是關於工時的，我只能回答說每間公司不同，不同部門與職位也不同。但這個問題讓我覺得有趣的點是，背後是不是想了解 work life balance 呢？ 這會是 GenZ 關注的重點嗎？</p><hr><p>事後想了幾個可以做更好的地方。</p><p>對方只有 16 歲，我講太多抽象概念了。我一直強調「不要把大腦外包」、唸書是在練習思考，老師當場提醒我要說清楚是思考「什麼」——才意識到這些話對我有意義，但對他們來說可能只是聽起來有道理的空話。</p><p>沒有 slide 真的很難控制節奏，以後類似場合主題要爛熟到不需要提示也能展開。</p><p>最後是發現打字寫作的時候可以把想法整理得很好，但講話的即時反應沒這麼快，需要多練習這種口頭報告。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>從一個 NiFi mTLS 問題搞懂 Keystore 與 Truststore</title>
      <link>https://weiblog.me/2026-05-04/nifi-mtls-keystore-truststore/</link>
      <description>今天幫同事解決了一個 NiFi 上設定 mTLS 的問題</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/NiFi/">NiFi</category>
      <category domain="https://weiblog.me/tags/mTLS/">mTLS</category>
      <category domain="https://weiblog.me/tags/TLS/">TLS</category>
      <category domain="https://weiblog.me/tags/%E6%86%91%E8%AD%89/">憑證</category>
      <category domain="https://weiblog.me/tags/Keystore/">Keystore</category>
      <category domain="https://weiblog.me/tags/Truststore/">Truststore</category>
      <enclosure url="https://weiblog.me/2026-05-04/nifi-mtls-keystore-truststore/image-0.png"/>
      <pubDate>Mon, 04 May 2026 10:25:58 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-05-04/nifi-mtls-keystore-truststore/image-0.png" class=""><div style="margin-bottom: 20px"></div><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>今天幫同事解決了一個 NiFi 上設定 mTLS 的問題。</p><p>由於公司最近換了內部的 CA，同事在更新憑證的過程中發現使用者無法用自己的憑證登入 NiFi，起初大家都覺得是 Server 憑證的問題，直覺告訴我和 Client 應該也有關。</p><h3 id="mTLS-是怎麼運作的"><a href="#mTLS-是怎麼運作的" class="headerlink" title="mTLS 是怎麼運作的"></a>mTLS 是怎麼運作的</h3><p>一般我們透過 SSL&#x2F;TLS 連線到網站，瀏覽器只需要驗證網站的憑證就好，Mutual TLS（mTLS）則是 Server 和 Client 互相驗證，用前面的例子來說，就是網站也需要驗證使用者的憑證。</p><p>先來看 TLS 的流程：</p><img src="/2026-05-04/nifi-mtls-keystore-truststore/tls-flow.svg" class="" title="TLS 握手流程"><p>再來看 mTLS 的流程：</p><img src="/2026-05-04/nifi-mtls-keystore-truststore/mtls-flow.svg" class="" title="mTLS 握手流程"><p>在 Server 發出 Certificate Request 時，如果有合適的憑證，就會要求使用者選擇。</p><h3 id="NiFi-的設定出了什麼問題"><a href="#NiFi-的設定出了什麼問題" class="headerlink" title="NiFi 的設定出了什麼問題"></a>NiFi 的設定出了什麼問題</h3><p>在 NiFi 的設定中可以透過 <code>nifi.security.keystore</code> 來選擇 Keystore，這邊的 Keystore 就是 NiFi Server 自己的 Private Key + 憑證的組合。</p><p>如果有設定 <code>nifi.security.needClientAuth</code> 為 <code>true</code> 的話，就會啟用 mTLS 要求 Client 提供憑證，而且需要另外設定信任的 CA <code>nifi.security.truststore</code>。</p><p>這次的問題就是出在我們把 Server 從舊憑證（CA1 發行）更新成新憑證（CA2）後，同時也把 <code>nifi.security.truststore</code> 換成只含 CA2 的版本。</p><p>這樣雖然網頁打得開，但一般使用者的憑證是由 CA1 發行的，NiFi 的 truststore 不認識 CA1，所以無法通過 mTLS 驗證。</p><p>解決方法是讓 <code>nifi.security.truststore</code> 同時信任 CA1 和 CA2，可以透過 <code>keytool</code>（Java 內建的憑證管理工具，用來對 Keystore &#x2F; Truststore 做新增、刪除、列出等操作）來匯入：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 將 CA1 匯入 truststore</span><br>keytool -importcert -<span class="hljs-built_in">alias</span> ca1 -file ca1.pem -keystore truststore.jks -storepass &lt;password&gt;<br><br><span class="hljs-comment"># 將 CA2 匯入 truststore</span><br>keytool -importcert -<span class="hljs-built_in">alias</span> ca2 -file ca2.pem -keystore truststore.jks -storepass &lt;password&gt;<br></code></pre></td></tr></table></figure><p>產生同時信任 CA1 和 CA2 的 Truststore，或是直接指定為 Root CA 也行，在公司內 CA1 和 CA2 的 Root CA 是同一個。</p><h3 id="Take-Away"><a href="#Take-Away" class="headerlink" title="Take Away"></a>Take Away</h3><ol><li>truststore 要涵蓋所有需要被驗證的對象的 CA，包含 Server 自己的以及 Client 的。</li><li>升級 CA 時，要確認 Client 也都更新憑證了再換過去，或是暫時設定成 Root CA。</li></ol><h3 id="後記"><a href="#後記" class="headerlink" title="後記"></a>後記</h3><p>很久沒有這樣解決問題了，像這樣解決別人的問題又能學到東西才是成長的動力。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>2026 福岡自由行 (下)</title>
      <link>https://weiblog.me/2026-04-30/2026-fukuoka-travel-3/</link>
      <description>2026 福岡自由行 Day 5, Day 6</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%97%85%E9%81%8A/">旅遊</category>
      <category domain="https://weiblog.me/tags/%E6%97%85%E9%81%8A/">旅遊</category>
      <category domain="https://weiblog.me/tags/%E6%94%9D%E5%BD%B1/">攝影</category>
      <category domain="https://weiblog.me/tags/%E6%97%A5%E6%9C%AC/">日本</category>
      <category domain="https://weiblog.me/tags/2026/">2026</category>
      <category domain="https://weiblog.me/tags/%E7%A6%8F%E5%B2%A1/">福岡</category>
      <enclosure url="https://weiblog.me/2026-04-30/2026-fukuoka-travel-3/image-0.jpg"/>
      <pubDate>Thu, 30 Apr 2026 15:06:31 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-04-30/2026-fukuoka-travel-3/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><p>寫到第三篇時，距離出遊已經過了快一個月，發現真的要馬上把當下的想法和感受都記下來，不然很多記憶快煙消雲散。</p><p>如果想看 KidZania 心得可以直接跳到 <a href="#Lalaport-KidZania">這裡</a></p><h1 id="Day-5"><a href="#Day-5" class="headerlink" title="Day 5"></a>Day 5</h1><p>因為油布院的商店要大約十點才開，所以規劃 Day 4 就把這邊走完，這樣隔天早上可以直接前往下一個景點。</p><h3 id="秋月城跡"><a href="#秋月城跡" class="headerlink" title="秋月城跡"></a>秋月城跡</h3><p>原本沒有排這個景點，但是聽福岡回來的同事說這裡也是個賞櫻名點，前一晚看了一下 Threads 的消息，感覺還行就決定前往。</p><p>看地圖離油布院沒有很遠，但開車從交流道下來到秋月城跡還是開了一小段小路，在城跡附近就有停車場，停車費 400 日幣，離場前自行去一個窗口繳費就好。</p><p>杉の馬場真的是兩排超密集的櫻花樹，可惜來晚了枝頭都已經冒出綠芽，如果是滿開一定非常漂亮。即使如此，從枝葉間穿透的陽光在地上形成的光影也非常美麗。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-1.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-2.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-3.jpg" class="">  </div></div><p>路上遇到的遊客幾乎都是日本人，有對爺爺奶奶兩個人一起來到這邊旅行，看到我們帶著小孩就來寒暄了幾句。</p><p>午餐在黑門茶屋解決，如果是旺季這間位在杉の馬場中的小店根本是不可能吃到的，特色是他的葛餅和蒸雜煮（有點像是茶碗蒸但放了很多料，甚至有麻糬？），點了烏龍麵給小孩吃，他的麵條一吃驚為天人，非常Q彈而且入味，甜點是一杯混合黑糖和當地食材的冰沙。</p><p>路上有個小插曲是我和太太撿地上櫻花嘗試要拍出櫻吹雪的感覺，坐在一旁的日本歐吉桑就說要示範給我們看怎麼拍，接著就起身去搖櫻花樹，此舉實在太驚人，但讓我想到台灣有個網紅只是扶著櫻花枝拍照就被台灣人罵死，結果日本人是…</p><h3 id="太宰府"><a href="#太宰府" class="headerlink" title="太宰府"></a>太宰府</h3><p>秋月城跡到太宰府天滿宮不遠，可以沿著山邊開小路過去，一邊欣賞難得的景色一邊開車，路上車非常少久久才看到對向有一台車經過。</p><p>因為和一般遊客來的方向不同，我們直接把車開到九州國立博物館後面的停車場，這樣一下就能走到天滿宮。停車場很酷的是沒有人收費，要繳費是拿著放在桌上的小袋子裝 500 日圓然後寫上車號後丟進桶子裡。</p><p>天滿宮人潮實在有夠多，參道上都是人，摸個御神牛都要排一條超長人龍讓我直接放棄，直接進入神社內。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-4.jpg" class="">  </div></div><p>主殿好像在維護，所以另外在前面搭建了一個臨時的主殿，並且把祀器都移出來了。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-5.jpg" class="">  </div></div><p>建築都很漂亮，超級飽和的橘色居然不會讓畫面很突兀。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-6.jpg" class="">  </div></div><p>為了要買限定招財貓特地跑去 Beams，結果發現店內幾乎沒什麼可以買的，商品都被掃光了，而那隻熱門橘色招財貓店員跟我說要等五天才會再進貨。</p><p>忘了先查好這邊要吃什麼，只好在路上隨便看隨便吃，是沒買到特別難吃的食物，但好像就這樣，梅枝餅滿有特色的，熱的吃和冷的吃是兩種不同的風味。</p><h3 id="Budget-租車"><a href="#Budget-租車" class="headerlink" title="Budget 租車"></a>Budget 租車</h3><p>經過豪豪推薦，這次在福岡選了 Budget，我覺得優點是據點很多，線上預約全中文介面，ETC 和兒童座椅都能一起按完，搭配旅遊促銷折扣下來還算便宜。</p><p>還車的時候發現走國道的過路費高達 7000 日幣，看來這趟也是很會跑。第二次日本自駕完全沒打錯雨刷和方向燈，特此紀念。</p><h3 id="博多麵街道．Shin-Shin-拉麵"><a href="#博多麵街道．Shin-Shin-拉麵" class="headerlink" title="博多麵街道．Shin Shin 拉麵"></a>博多麵街道．Shin Shin 拉麵</h3><p>因為飯店距離博多車站不遠，晚餐就去車站二樓的博多麵街道，上樓只能搭乘手扶梯對嬰兒車不友善，經過很多家看起來很厲害的拉麵店，但是看到那個座位就知道帶小孩的人無緣，聽說 Shin Shin 拉麵親子友善，這邊剛好也有。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-9.jpg" class="">  </div></div><p>預約後很快進去了，雖然沒有兒童座椅，但餐點選擇還算多，而且因為很吵完全不怕影響到其他人，在這邊享用了拉麵啤酒大餐，麵條是我吃過最細的拉麵麵條，但口味只能說還好。</p><h3 id="The-Basic"><a href="#The-Basic" class="headerlink" title="The Basic"></a>The Basic</h3><p>老飯店翻新，看很多網紅推薦過一直想定看看但是價格都很貴，但太太意外用七折撿到超大坪數房間，算一下發現櫻花季這樣買沒有特別貴還是咬牙訂下去了。</p><p>有點可惜就是樓層夠高但是沒有住到面運河的那側，但是這個空間大人小孩都很舒適，值得!</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-7.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-8.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-10.jpg" class="">  </div></div><h1 id="Day-6"><a href="#Day-6" class="headerlink" title="Day 6"></a>Day 6</h1><h3 id="Lalaport-KidZania"><a href="#Lalaport-KidZania" class="headerlink" title="Lalaport KidZania"></a>Lalaport KidZania</h3><p>從博多車站到 Lalaport 可以直接搭公車 (46 或 46L) 直接到達。</p><p>早上直衝 KidZania 讓小孩玩個過癮，門票不便宜兩大一小要將近 5000 日幣，進去後發現完全不知道預約的規則浪費了不少時間，建議想去的人一定要做好功課！</p><p>每個小朋友在 KidZania 可以有一個銀行帳戶，在這邊活動有分成兩種，就業（賺錢）和消費（花錢）。參與賺錢的活動後會獲得工資，可以存入銀行帳戶，裡面也有提款機可以領現金。參與消費活動時例如買筆、搭觀光巴士就需要刷卡付費。</p><p>在 KidZania 裡面所有活動都是要預約了，一個小朋友一次可以預約兩項體驗，但是只能「向前預約」，這是什麼意思？</p><p>規則是這樣，入場後每個小朋友會拿到一張預約卡，每個體驗都有場次開始和結束的時間，預約第一個活動完想要預約第二個時，只能挑選結束時間比第一個預約開始「早」的活動，簡單來說就是禁止連續卡位，而且去預約只能小朋友本人。</p><p>舉例來說：</p><div style="border-radius: 12px; overflow: hidden; font-family: sans-serif; margin: 16px 0; border: 1px solid #eee;">  <div style="background: #c0392b; color: white; padding: 10px 20px; font-size: 14px; font-weight: bold;">❌ 狀況一：會被拒絕</div>  <div style="background: #fafafa; padding: 16px 20px;">    <div style="display: flex; align-items: center; margin-bottom: 10px;">      <span style="background: #2f4154; color: white; border-radius: 6px; padding: 2px 10px; font-size: 12px; font-weight: bold; white-space: nowrap; margin-right: 12px;">第一個預約</span>      <span>消防員　10:00 ～ 11:00</span>    </div>    <div style="display: flex; align-items: center; margin-bottom: 14px;">      <span style="background: #2f4154; color: white; border-radius: 6px; padding: 2px 10px; font-size: 12px; font-weight: bold; white-space: nowrap; margin-right: 12px;">第二個預約</span>      <span>外科醫生　11:10 ～ 12:00</span>    </div>    <div style="background: #fdecea; color: #c0392b; border-radius: 8px; padding: 10px 14px; font-size: 13px;">外科醫生結束時間（12:00）晚於消防員開始時間（10:00），預約失敗</div>  </div></div><div style="border-radius: 12px; overflow: hidden; font-family: sans-serif; margin: 16px 0; border: 1px solid #eee;">  <div style="background: #27ae60; color: white; padding: 10px 20px; font-size: 14px; font-weight: bold;">✅ 狀況二：預約成功</div>  <div style="background: #fafafa; padding: 16px 20px;">    <div style="display: flex; align-items: center; margin-bottom: 10px;">      <span style="background: #2f4154; color: white; border-radius: 6px; padding: 2px 10px; font-size: 12px; font-weight: bold; white-space: nowrap; margin-right: 12px;">第一個預約</span>      <span>消防員　10:00 ～ 11:00</span>    </div>    <div style="display: flex; align-items: center; margin-bottom: 14px;">      <span style="background: #2f4154; color: white; border-radius: 6px; padding: 2px 10px; font-size: 12px; font-weight: bold; white-space: nowrap; margin-right: 12px;">第二個預約</span>      <span>牙醫　9:00 ～ 9:50</span>    </div>    <div style="background: #eafaf1; color: #27ae60; border-radius: 8px; padding: 10px 14px; font-size: 13px;">牙醫結束時間（9:50）早於消防員開始時間（10:00），預約成功</div>  </div></div><p>分享一下懶人攻略：</p><ol><li>事先查好想體驗的活動，這超級重要，因為熱門的像是消防員、Pizza 店、空服員這種會直接被預約到秒殺當天直接結束。</li><li>開門進去就直衝最想體驗活動預約。</li><li>如果還有時間在開始前去銀行開戶，取得金融卡。</li><li>如果前面還有時間可以插入第二個活動就去預約。</li></ol><p>午餐可以直接在裡面吃烏龍麵，簡單快速，因為一個活動體驗都差不多 40 ~ 50 分鐘，整天下來能玩到的有限，我們是滿幸運平日來人沒有很多，花了很多時間搞懂規則還能玩到四項，其中有一個是熱門的新生兒室照顧者。</p><p>但真的覺得超對不起小孩，兩位大人沒有先做功課搞懂玩法，以為現場排隊就能玩到好玩的，導致最後沒有玩到超好玩的消防員。</p><h3 id="Lalaport"><a href="#Lalaport" class="headerlink" title="Lalaport"></a>Lalaport</h3><p>最後，我不是很推薦在 Lalaport 購物逛街，這邊觀光客超級多，本來以為來這邊可以把我們想買的東西買齊，結果要什麼沒什麼，ABC Mart 所有 Salomon 的鞋子都沒尺寸，想帶盞 Akari 也都沒貨…</p><p>要說這邊的優點就是美食街很大，帶小孩吃東西一樣沒壓力，除此之外我認為沒有要拍牛鋼或是玩 KidZania 真的可以不用浪費時間過來。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-11.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-30/2026-fukuoka-travel-3/image-12.jpg" class="">  </div></div><p>最後離開 Lalaport 只帶了草莓回飯店。</p><h1 id="後記"><a href="#後記" class="headerlink" title="後記"></a>後記</h1><p>這趟講最多日文居然是在從飯店到機場這段時間，因為坐在前座可以跟司機好好聊天，原來想聊天就是要坐前座嗎？！</p><p>這次就先寫到這邊，感謝大家耐心閱讀，有想到什麼再補充。</p><div style="display: flex; gap: 12px; margin: 40px 0 0; font-family: sans-serif;">  <a href="/2026-04-11/2026-fukuoka-travel-1/" style="flex: 1; display: block; background: #f5f7f9; border-radius: 10px; padding: 14px 18px; text-decoration: none; color: #2f4154; border: 1px solid #e0e0e0;">    <div style="font-size: 11px; color: #888; margin-bottom: 4px;">上篇</div>    <div style="font-weight: bold; font-size: 14px;">2026 福岡自由行（上）</div>  </a>  <a href="/2026-04-21/2026-fukuoka-travel-2/" style="flex: 1; display: block; background: #f5f7f9; border-radius: 10px; padding: 14px 18px; text-decoration: none; color: #2f4154; border: 1px solid #e0e0e0;">    <div style="font-size: 11px; color: #888; margin-bottom: 4px;">中篇</div>    <div style="font-weight: bold; font-size: 14px;">2026 福岡自由行（中）</div>  </a></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>讓 AI 幫你寫 spec 前，你得先讀 spec</title>
      <link>https://weiblog.me/2026-04-29/ai-dev-spec-lesson/</link>
      <description>今天在開發一個新功能時犯了一個錯誤，代價是一個工作天</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/AI/">AI</category>
      <category domain="https://weiblog.me/tags/%E9%96%8B%E7%99%BC%E6%B5%81%E7%A8%8B/">開發流程</category>
      <category domain="https://weiblog.me/tags/spec/">spec</category>
      <enclosure url="https://weiblog.me/2026-04-29/ai-dev-spec-lesson/image-0.png"/>
      <pubDate>Wed, 29 Apr 2026 12:49:14 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-04-29/ai-dev-spec-lesson/image-0.png" class=""><div style="margin-bottom: 20px"></div><p>今天在開發一個新功能時犯了一個錯誤，代價是一個工作天。</p><p>事情是這樣的，我們需要同時在新舊系統上實作一個新功能，舊的系統已經由同事開發完成，我只需要在新系統上實作一樣的功能就好。</p><p>在我還沒看文件前，先看了一下程式碼沒有很多，想說偷懶一下全部交給 AI 來改好了，所以我的作法是這樣：</p><ol><li>用 <code>git diff</code> 找出有修改的地方，讓 AI 根據變動寫成 spec。</li><li>讓 AI 拆解成可以被單獨驗證的 tasks</li><li>執行</li></ol><p>一邊驗證測試一邊讓 AI 持續開發，但是驗證到某個功能的時候覺得不太對，某個功能在這次的改動應該要被移除怎麼還在，而且被改成四不像。於是回頭檢查是不是 spec 有錯，後來發現 spec 沒有明確寫到這個要被移除，而且在舊有的 contract 中有寫到這個功能，所以 AI 還是遵守 contract 把這個功能保留下來，但產出了錯誤的程式碼。</p><p>接著我嘗試了第二種作法：</p><ol><li>用 <code>git diff</code> 找出修改的地方，並且把變動的 before 和 after 也一起寫進 spec 文件中</li><li>要求寫 code 前要先更新 contract</li><li>執行</li></ol><p>我發現這樣在實作的速度上快很多，而且產生的 spec 文件非常好讀，很快就能做一些調整後進到開發。</p><p>紀錄一下這次的 lesson learned：</p><ol><li>如果是讓 AI 先進行一次 summarize 產生的 spec 如果太過抽象，可能會漏掉細節。</li><li>如果專案有用到 contract，當需求有變動時務必要更新 contract，可以把這個寫進 workflow 中避免這樣的事情。</li><li>最後是提醒自己一定要把規格和文件都讀完再來實作，不然連 review AI 產生的 spec 都沒辦法看出問題。</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>夏日大作戰復刻</title>
      <link>https://weiblog.me/2026-04-27/summer-wars-remaster/</link>
      <description>又又又看了一次夏日大作戰，這次是電影院</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/%E5%8B%95%E7%95%AB/">動畫</category>
      <category domain="https://weiblog.me/tags/%E9%9B%BB%E5%BD%B1/">電影</category>
      <enclosure url="https://weiblog.me/2026-04-27/summer-wars-remaster/image-0.png"/>
      <pubDate>Mon, 27 Apr 2026 15:26:15 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-04-27/summer-wars-remaster/image-0.png" class=""><div style="margin-bottom: 20px"></div><p>算上這次我已經數不出來看過幾次夏日大作戰了，記得第一次是大學的時候，窩在宿舍用 PPS 殘破不堪的畫質把它看完，這次則是第一次在電影院看。當熟悉的主題曲響起，彷彿跟著健二一起回到學生時期的夏天，重新喜歡上學姐 (?)</p><p>劇情沒有什麼轉折，流暢地講一個夏天發生在陣內家族的事，但每次看到奶奶抄起電話簿開始打電話以及花扎大戰的畫面，配上松本晃彥的音樂，眼淚都會不自覺流下來，我幾乎是每看必哭。</p><p>本來以為入場觀看的人會很少，出乎意料還滿多人的，我以為這是像我們這樣有點年紀的人才會想進來回味？真令人好奇他們是為了海報進來看，還是這個時間沒有其他選擇。</p><p>有一個運鏡是所有陣內家人坐在緣側上，鏡頭從右帶到左邊，照出所有人的剪影，這個畫面讓我印象深刻的是，畫面沒有用到明顯的透視，以及前後景的大小，單純利用物體間移動的速度差來表現遠近，我覺得這個手法真的非常厲害（如果有講錯請指正我）。</p><p>第一次領海報，原來電影院給的不會有任何塑膠套或是海報筒的嗎！？ 還好當天沒有下雨，順利帶上車回家了。</p><figure>  <img src="/2026-04-27/summer-wars-remaster/image-1.jpg" class="">  <figcaption>配上在宜得利撿的海報框，還滿搭的</figcaption></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>談談生產力</title>
      <link>https://weiblog.me/2026-04-23/on-productivity/</link>
      <description>Blog Blog 同樂會四月主題是生產力</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/Blog-Blog-%E5%90%8C%E6%A8%82%E6%9C%83/">Blog Blog 同樂會</category>
      <category domain="https://weiblog.me/tags/%E7%94%9F%E7%94%A2%E5%8A%9B/">生產力</category>
      <enclosure url="https://weiblog.me/2026-04-23/on-productivity/image-0.png"/>
      <pubDate>Thu, 23 Apr 2026 13:04:55 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-04-23/on-productivity/image-0.png" class=""><div style="margin-bottom: 20px"></div><h3 id="Blog-Blog-同樂會-四月的主題是-「生產力」，由-Wen-主持。"><a href="#Blog-Blog-同樂會-四月的主題是-「生產力」，由-Wen-主持。" class="headerlink" title="Blog Blog 同樂會 四月的主題是 「生產力」，由 Wen 主持。"></a><a href="https://blogblog.club/party/">Blog Blog 同樂會</a> 四月的主題是 <a href="https://www.wen-lab.tw/blogblog-party-productivity/">「生產力」</a>，由 <a href="https://www.wen-lab.tw/">Wen</a> 主持。</h3><br><br><p>談到生產力我會回想起數年前的工作，那是一個年輕人每天在燃燒自己獲得最大產出的情景，不知道是被責任感還是績效的焦慮推動著，時時刻刻都在想著不能浪費任何一點時間。</p><p>工作了一段時間，也閱讀了許多關於工作、人生的書籍，發現對於工作這件事情，是有很多種不同想法的。因此在這篇文章我想要來反思，生產力所為何事。</p><h3 id="生產力之於工作"><a href="#生產力之於工作" class="headerlink" title="生產力之於工作"></a>生產力之於工作</h3><p>談到生產力一定會先想到工作，所以在那之前先來談談工作。</p><p>在古希臘時期，比起「工作」，「閒暇」才是最重要的。亞里斯多德曾說過：「我們不處於閒暇，就是為了要處於閒暇。」換句話說 「工作就是為了不工作」，可以看出早期西方對工作的態度是「工作是一個不得已的手段」。</p><p>回到現代，我們被夢幻工作 (Dream Job) 吸引著，認為進到夢想中的公司就能獲得幸福、認為好的 Title 或者公司識別證能夠代表我們。每天奉獻大把時間，回到家卻只想上床睡覺，殷切地盼著週末來臨，然後開始下一個輪迴。</p><p>西方從鄙視工作到工作變成天命，這中間是發生了什麼變化？</p><p>韋伯在《新教倫理與資本主義精神》中解釋了喀爾文教派的邏輯：信徒為了確認自己是否能得救，瘋狂地在世俗工作中追求成功。而教會讓「勞動」從生存手段變成了神聖的「天職」。</p><p>這樣的觀念即使過了幾百年還殘留在我們的思想中，不知不覺變成沒有好好工作，就不算「成功」。有趣的是，在亞洲也鼓勵「勤勞是美德」，加上儒家及科舉制度，金榜題名就能光宗耀祖，這些思想沉澱了幾百年，讓我們不知不覺用工作來衡量一個人的成就。</p><p>現在回到生產力，如果說我們提高了生產力是為了「工作」，那我想反問，這些提高的生產力真的有讓我們遠離工作嗎？</p><h3 id="追求高效的代價"><a href="#追求高效的代價" class="headerlink" title="追求高效的代價"></a>追求高效的代價</h3><p>著名的帕金森定律告訴我們：「工作總會不斷膨脹，直到占滿所有可用的時間。」我們可能都有過這樣的經驗——下定決心要提早把事情做完，於是用番茄鐘保持專注，用時間箱規劃好每一個時段，終於把該做的都做完了。但主管發現有人手空出來，於是新的工作來了。效率提升的紅利，往往不是還給自己，而是被更多的工作填滿。</p><p>而過度追求高效，可能帶來負面的影響，作家摩根豪瑟在《一如既往》這本書中提到一個概念：「大部分事物都有自然發展的規模與速度，如果踰越限度，很快就會適得其反」。為了追求效率我們長期讓大腦滿載，我們以為多工可以不浪費任何一秒，但大腦在頻繁切換工作時也會造成損耗，沒有空出時間給大腦進行「發散模式」<sup id="fnref:1" class="footnote-ref"><a href="#fn:1" rel="footnote"><span class="hint--top hint--rounded" aria-label="大腦有兩種思考模式：「專注模式」用於解決已知問題，「發散模式」則在放鬆時運作，負責串聯想法與創意。參考《學習如何學習》。">[1]</span></a></sup>，長久下來注意力和創意下降，即使停下來休息也難以真正恢復。</p><h3 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h3><p>以前在專案中估工時，我都會估一個最樂觀的時間，以為這樣可以逼自己做更多。在重新了解工作與我的關係後，我越來越保守，還會刻意加上緩衝——不是因為變懶了，而是我知道「餘裕」的重要，是讓事情真正做好的空間。</p><p>生產力不是把每一秒都填滿，而是讓事情有空間好好完成。</p><section class="footnotes"><div class="footnote-list"><ol><li><span id="fn:1" class="footnote-text"><span>大腦有兩種思考模式：「專注模式」用於解決已知問題，「發散模式」則在放鬆時運作，負責串聯想法與創意。參考《學習如何學習》。<a href="#fnref:1" rev="footnote" class="footnote-backref"> ↩</a></span></span></li></ol></div></section>]]>
      </content:encoded>
    </item>
    <item>
      <title>2026 福岡自由行 (中)</title>
      <link>https://weiblog.me/2026-04-21/2026-fukuoka-travel-2/</link>
      <description>2026 福岡自由行 Day 3, Day 4</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%97%85%E9%81%8A/">旅遊</category>
      <category domain="https://weiblog.me/tags/%E6%97%85%E9%81%8A/">旅遊</category>
      <category domain="https://weiblog.me/tags/%E6%94%9D%E5%BD%B1/">攝影</category>
      <category domain="https://weiblog.me/tags/%E6%97%A5%E6%9C%AC/">日本</category>
      <category domain="https://weiblog.me/tags/2026/">2026</category>
      <category domain="https://weiblog.me/tags/%E7%A6%8F%E5%B2%A1/">福岡</category>
      <enclosure url="https://weiblog.me/2026-04-21/2026-fukuoka-travel-2/image-0.jpg"/>
      <pubDate>Tue, 21 Apr 2026 13:41:34 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-04-21/2026-fukuoka-travel-2/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><h1 id="Day-3"><a href="#Day-3" class="headerlink" title="Day 3"></a>Day 3</h1><h3 id="海之中道"><a href="#海之中道" class="headerlink" title="海之中道"></a>海之中道</h3><p>海之中道位在福岡市北邊，離市區有段距離，需要開車或搭乘大眾運輸前往，這次我們是開車前往，路程大約半小時。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-1.jpg" class="">  </div></div><p>自駕而且想租自行車的話可以考慮從東口、西口或奇妙世界入園，租車現在似乎沒有一日券了，一次租三小時，超時以一小時計價就看想玩多久。</p><p>我們很幸運剛好碰到櫻花季尾聲，可以一邊騎著腳踏車一邊欣賞櫻吹雪，因為是平日遊客沒有想像多，玩起來非常舒服。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-2.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-3.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-5.jpg" class="">  </div></div><p>園區內有很多大型兒童遊具、可愛動物區、水族館等等，如果是帶小孩來玩建議停留半天到一天，在奇妙世界可以買點東西吃。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-4.jpg" class="">  </div></div><h3 id="博多運河城"><a href="#博多運河城" class="headerlink" title="博多運河城"></a>博多運河城</h3><p>因為飯店停車場非常便宜但車位很少，在四點左右把車停回飯店接著散步去運河城逛街吃飯。</p><p>運河城還算好逛，是這次旅程僅次於天神的逛街地點，但逛一逛後看了一下時間，不知道哪根筋不對，發現好像有機會去吃一蘭，退完稅後就直奔位於中州的一蘭本店。沿著運河走，一邊欣賞河邊的建築一邊散步拍照，我想這就是看不到表演的原因吧。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-6.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-7.jpg" class="">  </div></div><h3 id="一蘭拉麵總本店"><a href="#一蘭拉麵總本店" class="headerlink" title="一蘭拉麵總本店"></a>一蘭拉麵總本店</h3><p>大約六點四十到一蘭門口，已經有很多人在排隊了，有很多韓國人和香港人，感覺台灣人是少數。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-8.jpg" class="">  </div></div><p>店員會把推娃娃車的客人排到一樓，店內有兒童座椅，適合一家人來吃。</p><p>一蘭有個特別的活動是只要下載一蘭 APP （iOS 需要在日本 App Store 下載）可以獲得兒童拉麵券，趁這次去體驗了一下兒童拉麵到底是怎樣。</p><p>本來興高采烈期待會吃到傳說中的五角碗，但後來才知道是太宰府店才有。因為排隊排太久最後連八點的表演也沒看到，還在外面吹風吹了一個小時，為了吃拉麵也沒在運河城逛個過癮。</p><p>吃一蘭是這次旅行第二個錯誤決定。</p><h1 id="Day-4"><a href="#Day-4" class="headerlink" title="Day 4"></a>Day 4</h1><p>早上退房完就發生一件令人膽戰心驚的事情，正當我們享用完早餐準備把行李搬上車時，發現放在置物區的行李箱不見了，外頭停了一台遊覽車，而司機正在一個接一個把地上的行李弄上車，原來是一團雄獅的旅行團準備要離開，把我們的行李也當成了他們團員的，我只好一件一件檢查，幸好手把上有掛一個 Line Friends 的吊牌，可以在遊覽車底快速的找到我們的行李。</p><p>說實話在看到遊覽車之前，我根本不知道有團客入住，加上放行李的地方都是吃早餐的人臨時放置的區域，他們這團也沒有作記號或是把行李圍起來，我們的行李就這樣被誤認了。</p><p>雖然我自己覺得很扯就是，難道不該是團客吃完早餐後把自己的行李推出去嗎，不是自己的就別碰。</p><p>這次學到兩個教訓：</p><ol><li>行李箱一定要有特別的記號，掛東西或是特殊貼紙都好</li><li>放行李的時候留意一下</li></ol><h3 id="九州自然動物園"><a href="#九州自然動物園" class="headerlink" title="九州自然動物園"></a>九州自然動物園</h3><p>這個動物園的賣點就是「マイカーサファリ」 也就是自駕遊園啦，意思就是可以開著自己的車進去繞園區一圈，這邊的動物在園區內是可以自由活動的，也就是說可能會遇到動物跑到路上造成汽車大排長龍這種事，畢竟這是他家嘛。 正常開車繞一圈走走停停加拍照大約花費四十分鐘到一小時。</p><figure style="text-align: center; margin-bottom: 10px">  <img src="/2026-04-21/2026-fukuoka-travel-2/image-11.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">就是這種感覺</figcaption></figure><p>除了開車探險外，園區內還有叢林巴士（ジャングルバス）可以搭乘，搭乘叢林巴士可以近距離餵食動物，我們因為沒有預定到，當天大約十一點到整天的車次都賣光了，建議要搭乘叢林巴士的人一定要提前預約，或是當天早一點到。</p><p>每一區都會有工作人員駕駛的斑馬車在旁邊待命。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-9.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-10.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-12.jpg" class="">  </div></div><p>動物園我們只排半天多一點，開車慢慢繞了一圈吃完午餐後就前往由布院了，順帶一提，餐廳長得很像員工餐廳，但是漢堡排非常好吃。</p><h3 id="由布院"><a href="#由布院" class="headerlink" title="由布院"></a>由布院</h3><p>從九州動物園到由布院大約半小時，來玩動物園的人通常會住在別府或是由布院。</p><p>中間又發生一個驚魂記，在找停車場的過程中開過頭，路上又是單線道實黃線，我不敢迴轉掉頭，就一直開下去想說前面應該有地方可以迴轉，但越開越不對，開始爬山了還是沒有看到任何路口可以迴轉，只好在旁邊的避車灣等待時機迴轉。最後還是跨了實黃線迴轉…..</p><p>去民宿 Check In 完已經三點多了，剛好附近就有櫻花河堤，趁著還有光線我們沿著河堤前往最有名的湯之坪街道。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-15.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-16.jpg" class="">  </div></div><p>湯之坪街道和想像的有點不一樣，但也說不上為什麼，大概就是一條小小的街兩側都是商店，可以眺望遠處的由布岳。人潮很多，從語言上可以輕易判斷是韓國人還是香港人，日本人感覺都是在地人不是遊客。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-13.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-21/2026-fukuoka-travel-2/image-14.jpg" class="">  </div></div><p>因為走到湯之坪的時間已經有點晚了加上隔天早上想直接衝秋月城跡，就乾脆把花卉村、金麟湖也一起走完，大約五點多遊客就越來越少了，店家也陸續在打烊。</p><p>很糗的是，想說回程可以在車站附近買晚餐回去，殊不知走到這邊店也幾乎都打烊了，經過 Lawson 時還猶豫了一下要不要進去，這是這趟旅程第三個錯誤的決定：我們決定繼續走下去碰碰運氣，結果就是地圖上標了很多餐廳，但一間都沒開，只買了一份貴貴鰻魚飯回去民宿三個人分，差點沒被交誼廳傳來的泡麵味香死。</p><p>這次住的民宿就有湯屋，可以全家人一起進去洗澡泡湯，另外有兒童遊戲室，女兒第一次體驗在榻榻米和室中玩耍，玩到關門還不想回房睡覺。</p><figure style="text-align: center; margin-bottom: 10px">  <img src="/2026-04-21/2026-fukuoka-travel-2/image-17.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">隔天早上趁家人還在睡覺自己跑出來散步拍照</figcaption></figure><h1 id="後記"><a href="#後記" class="headerlink" title="後記"></a>後記</h1><p>覺得油布院這邊時間排太少有點可惜，我們走到金麟湖的路上發現很多很好逛的小店，可惜都已經打烊，今天的時間也不足以讓我們好好逛，如果來這邊會想逛街的話，建議預留多一點時間逛逛走走。</p><div style="display: flex; gap: 12px; margin: 40px 0 0; font-family: sans-serif;">  <a href="/2026-04-11/2026-fukuoka-travel-1/" style="flex: 1; display: block; background: #f5f7f9; border-radius: 10px; padding: 14px 18px; text-decoration: none; color: #2f4154; border: 1px solid #e0e0e0;">    <div style="font-size: 11px; color: #888; margin-bottom: 4px;">上篇</div>    <div style="font-weight: bold; font-size: 14px;">2026 福岡自由行（上）</div>  </a>  <a href="/2026-04-30/2026-fukuoka-travel-3/" style="flex: 1; display: block; background: #f5f7f9; border-radius: 10px; padding: 14px 18px; text-decoration: none; color: #2f4154; border: 1px solid #e0e0e0;">    <div style="font-size: 11px; color: #888; margin-bottom: 4px;">下篇</div>    <div style="font-weight: bold; font-size: 14px;">2026 福岡自由行（下）</div>  </a></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>【學習如何學習】讀後心得</title>
      <link>https://weiblog.me/2026-04-15/learning-how-to-learn-reading-notes/</link>
      <description>本文為《學習如何學習》一書的讀後感</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E8%AE%80%E6%9B%B8%E5%BF%83%E5%BE%97/">讀書心得</category>
      <category domain="https://weiblog.me/tags/%E5%AD%B8%E7%BF%92/">學習</category>
      <category domain="https://weiblog.me/tags/%E8%AE%80%E6%9B%B8%E5%BF%83%E5%BE%97/">讀書心得</category>
      <enclosure url="https://weiblog.me/2026-04-15/learning-how-to-learn-reading-notes/image-0.png"/>
      <pubDate>Wed, 15 Apr 2026 13:07:48 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-04-15/learning-how-to-learn-reading-notes/image-0.png" class=""><div style="margin-bottom: 20px"></div><h2 id="關於這本書"><a href="#關於這本書" class="headerlink" title="關於這本書"></a>關於這本書</h2><p>作者芭芭拉‧歐克莉是密西根大學教授，也是暢銷書《大腦喜歡這樣學》的作者，本書就是她與其他作者一起將「Learning How to Learn」這門課程的精華，以淺顯易懂的方式整理寫給年輕讀者。</p><p>書中不僅從神經科學的角度解釋了大腦是怎麼儲存記憶的，也提供了許多能夠幫助學習的技巧。</p><p>這邊列出本書最重要的幾點：</p><ol><li>想要有效學習，最重要的就是要「專注」，專注是任何有效學習的重點。</li><li>大腦會在「專注」和「發散」模式中切換，專注模式進行深度思考，發散模式則是讓思緒任意遊走。</li><li>針對不同主題進行「交錯」學習，可以混入不同的思考方式，有助於提升理解。</li><li>運動可以提供神經元需要的營養物質 BDNF，讓神經元長得更大更強健。</li><li>刻意練習： 針對較難的教材持續且重複學習會比「懶惰的學習」更能刺激神經元生長。</li><li>積極回想： 不斷的讓大腦去回想已經學過的東西，可以將資訊放入記憶中。</li><li>番茄鐘工作法是一個符合腦科學的方法，讓我們可以在專注和發散模式間切換。</li></ol><h2 id="如果只能畫一條重點"><a href="#如果只能畫一條重點" class="headerlink" title="如果只能畫一條重點"></a>如果只能畫一條重點</h2><blockquote><p>專注模式和發散模式。大腦以兩種方式運作：專注和發散模式。你可以將它們想像成緩衝器密集和分散設置的兩種彈珠檯。我們需要轉換這兩種模式，以便能夠有效學習</p></blockquote><p>我認為了解大腦的這兩種模式可以提升我的學習成效，因為沒有人天生就知道要刻意在兩種模式間切換可以提升學習效果，過去唸書的方法可能都是埋頭苦讀，一次就是 K 一個上午的書，沒有人告訴我放空和專注一樣重要。</p><h2 id="和我的關係"><a href="#和我的關係" class="headerlink" title="和我的關係"></a>和我的關係</h2><p>讀完這本書後，開始嘗試書中提到的閱讀方式： 先用文本散步把章節標題都掃過，在腦中建立基本印象，接著在專注讀完後透過積極回想來強化記憶。</p><p>我會更積極的使用番茄鐘來學習和工作，強迫自己進入「專注」和「發散」的循環，只要想到，隨時隨地都能練習「積極回想」這個技巧，讓它變成安裝在腦中的預設軟體。</p><p>這本書沒有很多頁，但是非常多精彩的重點，真希望過去在唸書的時期就能夠學到這些學習技巧，可以有大把的時間練習和運用。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>自己的 AI 工作流才是真正的護城河</title>
      <link>https://weiblog.me/2026-04-12/ai-workflow-is-your-ip/</link>
      <description>讀 Kaxil 的文章 I Haven't Written a Line of Code in 4 Months 後的筆記</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E8%AE%80%E6%9B%B8%E5%BF%83%E5%BE%97/">讀書心得</category>
      <category domain="https://weiblog.me/tags/AI/">AI</category>
      <category domain="https://weiblog.me/tags/Roo-Code/">Roo Code</category>
      <category domain="https://weiblog.me/tags/Claude-Code/">Claude Code</category>
      <enclosure url="https://weiblog.me/2026-04-12/ai-workflow-is-your-ip/image-0.png"/>
      <pubDate>Sun, 12 Apr 2026 15:25:01 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-04-12/ai-workflow-is-your-ip/image-0.png" class=""><div style="margin-bottom: 20px"></div><h2 id="為什麼會想讀這篇文章"><a href="#為什麼會想讀這篇文章" class="headerlink" title="為什麼會想讀這篇文章"></a>為什麼會想讀這篇文章</h2><p>在現在人人都在用 AI 加速的時代，實在很想知道別人是怎麼使用這些工具在開發的，在社群平台看到有人分享了太空人的 Kaxil 寫的文章讓我很感興趣。</p><p>文章中有段文字讓我印象深刻：</p><blockquote><p><strong>Your workflow IS your IP.</strong></p></blockquote><p>你怎麼使用 AI 工具的方式，才是你真正的競爭優勢，換句話說工具人人都能用，但能設計出高效 workflow 的人才是少數。</p><h2 id="Take-Away"><a href="#Take-Away" class="headerlink" title="Take Away"></a>Take Away</h2><h3 id="1-Skills：把你的知識轉成可重複執行的指令"><a href="#1-Skills：把你的知識轉成可重複執行的指令" class="headerlink" title="1. Skills：把你的知識轉成可重複執行的指令"></a>1. Skills：把你的知識轉成可重複執行的指令</h3><p>Skills 是一組明確的指示，告訴 AI 在特定情境下應該怎麼做，同時 Skill 可以呼叫其他 Skill，形成組合技。</p><p>這就像是把你的工作 SOP 編碼化。你只需要設計一次，之後每次執行都能得到一致的結果，不用每次都重新解釋背景和期望。</p><p>以這個部落格為例，我就設計了幾個 Skill，例如「幫文章插入封面圖」、「依照 Kevin 的寫作風格潤稿」。每個 Skill 都有明確的輸入和輸出，組合起來就能完成完整的發文流程。</p><h3 id="2-Hooks：讓-AI-不會暴衝的護欄"><a href="#2-Hooks：讓-AI-不會暴衝的護欄" class="headerlink" title="2. Hooks：讓 AI 不會暴衝的護欄"></a>2. Hooks：讓 AI 不會暴衝的護欄</h3><p>這是我覺得最實用的一個概念。</p><p>在平常使用 Roo Code 開發的過程中，發現即使在 Skill 裡寫了「遇到 A 情況就要執行 B」 這樣明確的指示，AI 有時候還是會選擇忽略它，這對 AI 來說沒有強制力，如果沒有自己看程式碼檢查就不會注意到這個不該犯錯的地方。</p><p>和 Skill 不一樣的是，Hooks 是在事件觸發時，由工具本身自動執行的 shell 指令。例如：每次 AI 寫完一段 code 就自動編譯或執行測試，沒過就無法繼續，這是系統層面的護欄。</p><h3 id="3-不同模型，不同用途"><a href="#3-不同模型，不同用途" class="headerlink" title="3. 不同模型，不同用途"></a>3. 不同模型，不同用途</h3><p>不是每件事都要用最強的模型。以 Claude Code 為例，Kaxil 的觀察是：</p><ul><li><strong>Sonnet</strong>：適合寫 code、日常任務，速度快、夠用</li><li><strong>Opus</strong>：適合深度規劃、架構設計，思考更全面</li></ul><p>用 Opus 寫 code 反而容易 overengineering，AI 會想得太複雜。選對模型不只是省錢，也是在保護你的結果品質。</p><p>除了開發，Kaxil 也把 AI 整合進更多日常工作，像是自動把會議記錄寫進 Obsidian。工作流不只是 coding，而是整個知識管理和溝通流程。</p><h2 id="對我的影響"><a href="#對我的影響" class="headerlink" title="對我的影響"></a>對我的影響</h2><p>學習到了 Hooks 的用法 (因為平常工作使用的 Roo Code 工作流沒有這個功能)。在 Skill 裡寫各種規範，但 AI 不一定會遵守，尤其在執行到一半、遇到模糊問題的時候，很容易就憑感覺自由發揮了。</p><p>這也給了我在公司開發的 side project – Roo Code CLI 一些靈感，例如實作 hooks 機制以及自動切換模型，讓整個 workflow 的可靠性會大幅提升。</p><p>讀到這段像是當頭棒喝，目前 AI 的產出速度已經超過我的 review 速度了，有時候測試跑過了就會偷懶不 review (So bad!)</p><blockquote><p>Learn to review code you didn’t write. This was always important. Now it’s the whole job.</p></blockquote><p>看樣子還是要認真把每一行看過。</p><p>最後複習這段我最喜歡的一句話:</p><blockquote><p>Your workflow IS your IP.</p></blockquote><p>這給了我一個明確的學習方向： 創造自己的工作流</p><h2 id="參考資料"><a href="#參考資料" class="headerlink" title="參考資料"></a>參考資料</h2><ul><li>原文：<a href="https://kaxil.substack.com/p/i-havent-written-a-line-of-code-in">I Haven’t Written a Line of Code in 4 Months (But I Ship More Than Ever)</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>2026 福岡自由行 (上)</title>
      <link>https://weiblog.me/2026-04-11/2026-fukuoka-travel-1/</link>
      <description>2026 福岡自由行 Day 1, Day 2</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%97%85%E9%81%8A/">旅遊</category>
      <category domain="https://weiblog.me/tags/%E6%97%85%E9%81%8A/">旅遊</category>
      <category domain="https://weiblog.me/tags/%E6%94%9D%E5%BD%B1/">攝影</category>
      <category domain="https://weiblog.me/tags/%E6%97%A5%E6%9C%AC/">日本</category>
      <category domain="https://weiblog.me/tags/2026/">2026</category>
      <category domain="https://weiblog.me/tags/%E7%A6%8F%E5%B2%A1/">福岡</category>
      <enclosure url="https://weiblog.me/2026-04-11/2026-fukuoka-travel-1/image-0.jpg"/>
      <pubDate>Sat, 11 Apr 2026 12:37:53 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-04-11/2026-fukuoka-travel-1/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><p>原本沒有打算在清明連假出遊，但二月時不知道哪來的衝動訂了福岡的機票，和東京大阪相比福岡的班機選擇真的比較少，票價還不便宜。</p><p>一直以來都在 Instagram 上被推播各種福岡飯店名單，但因為太晚訂又遇到櫻花季尾聲，選擇實在不多，在快速規劃完想玩的景點後馬上把飯店刷了。</p><p>這次的行程大致如下，遊記會分成上中下三部。</p><div style="border-radius: 12px; overflow: hidden; font-family: sans-serif; margin: 24px 0;">  <div style="background: #2f4154; color: white; padding: 12px 20px; font-size: 14px; font-weight: bold; letter-spacing: 1px;">行程總覽</div>  <div style="background: #fafafa; padding: 8px 0;">    <div style="display: flex; align-items: flex-start; padding: 10px 20px; border-bottom: 1px solid #eee;">      <div style="background: #2f4154; color: white; border-radius: 6px; padding: 3px 10px; font-size: 12px; font-weight: bold; white-space: nowrap; margin-right: 14px; margin-top: 2px;">Day 1<br>4/4</div>      <div style="color: #444;">✈️ 去程 A321</div>    </div>    <div style="display: flex; align-items: flex-start; padding: 10px 20px; border-bottom: 1px solid #eee;">      <div style="background: #2f4154; color: white; border-radius: 6px; padding: 3px 10px; font-size: 12px; font-weight: bold; white-space: nowrap; margin-right: 14px; margin-top: 2px;">Day 2<br>4/5</div>      <div style="color: #444;">舞鶴公園 ・ 麵包超人博物館 ・ 天神</div>    </div>    <div style="display: flex; align-items: flex-start; padding: 10px 20px; border-bottom: 1px solid #eee;">      <div style="background: #2f4154; color: white; border-radius: 6px; padding: 3px 10px; font-size: 12px; font-weight: bold; white-space: nowrap; margin-right: 14px; margin-top: 2px;">Day 3<br>4/6</div>      <div style="color: #444;">🚗 海之中道 ・ 博多運河城</div>    </div>    <div style="display: flex; align-items: flex-start; padding: 10px 20px; border-bottom: 1px solid #eee;">      <div style="background: #2f4154; color: white; border-radius: 6px; padding: 3px 10px; font-size: 12px; font-weight: bold; white-space: nowrap; margin-right: 14px; margin-top: 2px;">Day 4<br>4/7</div>      <div style="color: #444;">🚗 九州動物園 ・ 由布院</div>    </div>    <div style="display: flex; align-items: flex-start; padding: 10px 20px; border-bottom: 1px solid #eee;">      <div style="background: #2f4154; color: white; border-radius: 6px; padding: 3px 10px; font-size: 12px; font-weight: bold; white-space: nowrap; margin-right: 14px; margin-top: 2px;">Day 5<br>4/8</div>      <div style="color: #444;">🚗 秋月城跡 ・ 太宰府天滿宮</div>    </div>    <div style="display: flex; align-items: flex-start; padding: 10px 20px; border-bottom: 1px solid #eee;">      <div style="background: #2f4154; color: white; border-radius: 6px; padding: 3px 10px; font-size: 12px; font-weight: bold; white-space: nowrap; margin-right: 14px; margin-top: 2px;">Day 6<br>4/9</div>      <div style="color: #444;">LaLaport</div>    </div>    <div style="display: flex; align-items: flex-start; padding: 10px 20px;">      <div style="background: #2f4154; color: white; border-radius: 6px; padding: 3px 10px; font-size: 12px; font-weight: bold; white-space: nowrap; margin-right: 14px; margin-top: 2px;">Day 7<br>4/10</div>      <div style="color: #444;">✈️ 回程 777-300ER</div>    </div>  </div></div><h1 id="Day-1"><a href="#Day-1" class="headerlink" title="Day 1"></a>Day 1</h1><p>原本小擔心清明連假北上桃機會塞車，意外地一路順暢，難得遇到一個不會亂開的機場接送司機，在車上舒服的睡了一覺。習慣上會提前兩小時多到機場報到，有充裕的時間可以在二航美食街慢慢閒晃，走著走著居然瞄到了雙月，而且還有供應雞湯和愛恨交織麵！</p><p>去程是搭空巴 A321，機體很小又沒有娛樂系統，原本搭飛機就會很緊張的我在上面如坐針氈，靠著手機上的單機小遊戲成功度過了這兩小時。</p><p>到達福岡機場大約是六點多，在排隊入境時看到有機邊托運娃娃車的家長都拿到車了但我們手上沒有，很緊張是不是我們下空橋應該要在那邊等，好在在行李轉盤旁邊發現我們的推車，而且是展開的狀態（地勤原來知道怎麼展開嗎？）。</p><p>福岡機場因為靠近市區很方便，飯店在附近的話可以直接搭計程車節省時間。但就在和司機溝通飯店時發生了小插曲，我們住的飯店有很多家分店，司機大哥以為就是博多市區那家，剛好分店中洲川端 (なかすかわばた) 漢字不會念，司機看地址看了很久才成功輸入到導航。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-1.jpg" class="">  </div></div><p>因為沒有很餓晚餐就去附近便利商店隨便買，順路去附近超市找找有沒有新鮮草莓，途中經過冷泉公園，雖然沒有點燈，但櫻花樹在路燈照射下依舊很美。</p><h1 id="Day-2"><a href="#Day-2" class="headerlink" title="Day 2"></a>Day 2</h1><h3 id="櫛田神社"><a href="#櫛田神社" class="headerlink" title="櫛田神社"></a>櫛田神社</h3><p>櫛田神社是舉辦博多祇園山笠祭典重要的場所，剛好在飯店附近，在搭地鐵前順道去走走。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-2.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-3.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-4.jpg" class="">  </div></div><figure style="text-align: center; margin-bottom: 10px; width: 60%; margin-left: auto; margin-right: auto">  <img src="/2026-04-11/2026-fukuoka-travel-1/image-5.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">裡面的山笠約 10 ~ 15 公尺高，非常壯觀。</figcaption></figure><div style="width: 60%; margin: 0 auto 10px">  <img src="/2026-04-11/2026-fukuoka-travel-1/image-6.jpg" class=""></div><h3 id="舞鶴公園"><a href="#舞鶴公園" class="headerlink" title="舞鶴公園"></a>舞鶴公園</h3><p>接近櫻花季尾聲，決定調整行程先去舞鶴公園欣賞櫻花。天氣很好，但適逢假日遊客很多，櫻花樹下許多人帶著野餐墊和食物在樹下野餐。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-7.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-8.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-9.jpg" class="">  </div></div><figure style="text-align: center; margin-bottom: 10px">  <img src="/2026-04-11/2026-fukuoka-travel-1/image-12.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">終於解鎖了，在櫻花樹下喝酒的人生清單。 桜より… ビール！</figcaption></figure><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-13.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-14.jpg" class="">  </div></div><p>賞櫻過程臭寶一直鬧事，一天下來不知道講了幾次「下次不帶你出門了」。</p><p>因為時間不夠，只能一邊遙望隔壁大濠公園滿開的櫻堤，一邊前往車站。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-10.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-11.jpg" class="">  </div></div><h3 id="麵包超人博物館"><a href="#麵包超人博物館" class="headerlink" title="麵包超人博物館"></a>麵包超人博物館</h3><p>有帶小孩到福岡必去的景點，在博多 Riverain 百貨的五樓，可以從中洲川端站六號出口直接搭電梯前往。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-15.jpg" class="">  </div></div><figure style="text-align: center; margin-bottom: 10px">  <img src="/2026-04-11/2026-fukuoka-travel-1/image-17.jpg" class="">  <figcaption style="color: #888; font-size: 13px; margin-top: 6px">專用推車停車場</figcaption></figure><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-16.jpg" class="">  </div></div><p>裡面可以玩的設施很多，也有陪玩姊姊，如果小朋友想盡情放電，推薦抓個兩小時以上。</p><p>SL 號小火車在入場時會拿到一張整理券，到指定搭乘時段後可以去月台搭乘，雖然說是月台，其實就是 Riverain 建築的一樓，要從特定的電梯搭下去一樓，到廣場後就會看到工作人員接待。</p><h3 id="天神"><a href="#天神" class="headerlink" title="天神"></a>天神</h3><p>本來應該是要來這邊盡情逛街的，但昨天吃壞肚子加上沒睡好，準備開逛時頭痛欲裂，兩個人只想隨便解決晚餐趕快回飯店睡覺。</p><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-18.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-19.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center; margin-bottom: 10px">  <div style="padding: 5px; width: 90%">    <img src="/2026-04-11/2026-fukuoka-travel-1/image-20.jpg" class="">  </div></div><p>本來想說後面還有運河城和 Lalaport 可以逛，天神踩個點就好，事後回想這整趟旅行最好逛的就是這了。</p><div style="display: flex; gap: 12px; margin: 40px 0 0; font-family: sans-serif;">  <a href="/2026-04-21/2026-fukuoka-travel-2/" style="flex: 1; display: block; background: #f5f7f9; border-radius: 10px; padding: 14px 18px; text-decoration: none; color: #2f4154; border: 1px solid #e0e0e0;">    <div style="font-size: 11px; color: #888; margin-bottom: 4px;">中篇</div>    <div style="font-weight: bold; font-size: 14px;">2026 福岡自由行（中）</div>  </a>  <a href="/2026-04-30/2026-fukuoka-travel-3/" style="flex: 1; display: block; background: #f5f7f9; border-radius: 10px; padding: 14px 18px; text-decoration: none; color: #2f4154; border: 1px solid #e0e0e0;">    <div style="font-size: 11px; color: #888; margin-bottom: 4px;">下篇</div>    <div style="font-weight: bold; font-size: 14px;">2026 福岡自由行（下）</div>  </a></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>理想的日常</title>
      <link>https://weiblog.me/2026-03-29/perfect-days/</link>
      <description>Blog Blog 同樂會三月主題理想的日常</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E7%94%9F%E6%B4%BB/">生活</category>
      <category domain="https://weiblog.me/tags/Blog-Blog-%E5%90%8C%E6%A8%82%E6%9C%83/">Blog Blog 同樂會</category>
      <enclosure url="https://weiblog.me/2026-03-29/perfect-days/image-0.png"/>
      <pubDate>Sat, 28 Mar 2026 23:45:56 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-03-29/perfect-days/image-0.png" class=""><div style="margin-bottom: 20px"></div><h3 id="受到李唯邀請參加-Blog-Blog-同樂會-的投稿活動，三月的主題是-「理想的日常」，由-Alex-Hsu-主持。"><a href="#受到李唯邀請參加-Blog-Blog-同樂會-的投稿活動，三月的主題是-「理想的日常」，由-Alex-Hsu-主持。" class="headerlink" title="受到李唯邀請參加 Blog Blog 同樂會 的投稿活動，三月的主題是 「理想的日常」，由 Alex Hsu 主持。"></a>受到<a href="https://blog.wei-lee.me/">李唯</a>邀請參加 <a href="https://blogblog.club/party/">Blog Blog 同樂會</a> 的投稿活動，三月的主題是 <a href="https://alexhsu.com/perfect-days">「理想的日常」</a>，由 <a href="https://alexhsu.com/">Alex Hsu</a> 主持。</h3><br><br><blockquote><p>「週日早上和太太及家人一起在院子草地上野餐，小孩在一旁玩耍，我則獲得一個在陽光下悠哉看書的下午。有時下去陪小孩溜滑版，有時坐著思考。這是一個非常棒的週末早晨」</p></blockquote><br><p>說到理想的日常會讓我想起今年年初在一篇「最棒的週末」筆記中寫到的這段話。</p><p>沒有出門旅遊或是參與特別的活動，暫時放下了各種煩惱只專注在當下：有小孩、有溫暖的陽光、涼爽的微風還有短暫的空閒可以閱讀。</p><p>「那麼有辦法讓每天都感受到幸福嗎，天天都是理想的日常嗎？」</p><p>我認為是可以的，但前提是要先把感受幸福的能力找回來——而這需要一點自律。讓像是陪小孩玩、手沖咖啡、閱讀甚至是洗奶瓶這些稀鬆平常的瑣事也能夠創造出幸福感。</p><br><blockquote><p>生活の中に個人的な「小確幸」を見出すためには、多かれ少なかれ自己規制みたいなものが必要とされる。</p><p>—— 村上春樹《蘭格漢斯島の午後》</p></blockquote><br><p>最近讀了些書，才發現村上說的自律，和校正爽痛平衡的概念不謀而合，也許村上先生早就領悟到了吧。</p><p>能夠無時無刻感受這些微小的幸福，就是我理想的日常。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>電玩排毒 30 天</title>
      <link>https://weiblog.me/2026-03-24/game-detox-30-days/</link>
      <description>今天正是電玩排毒的第三十天，紀錄一下!</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/%E8%87%AA%E6%88%91%E6%88%90%E9%95%B7/">自我成長</category>
      <category domain="https://weiblog.me/tags/%E5%A4%9A%E5%B7%B4%E8%83%BA/">多巴胺</category>
      <category domain="https://weiblog.me/tags/%E7%BF%92%E6%85%A3%E9%A4%8A%E6%88%90/">習慣養成</category>
      <enclosure url="https://weiblog.me/2026-03-24/game-detox-30-days/image-0.png"/>
      <pubDate>Tue, 24 Mar 2026 14:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-03-24/game-detox-30-days/image-0.png" class=""><div style="margin-bottom: 20px"></div><p>今天正是電玩排毒 - 刪光電腦中所有遊戲後的第三十天，紀錄一下。</p><h3 id="為什麼會開始電玩排毒"><a href="#為什麼會開始電玩排毒" class="headerlink" title="為什麼會開始電玩排毒"></a>為什麼會開始電玩排毒</h3><p>總是想著有好多事情想做以及學習，但是自己處於一種一放鬆就想打電動的「殭屍模式」<sup id="fnref:1" class="footnote-ref"><a href="#fn:1" rel="footnote"><span class="hint--top hint--rounded" aria-label="大腦放空、自動導航，不用思考地重複同樣行為。">[1]</span></a></sup>。<br>在閱讀<a href="/2026-03-09/dopamine-nation/">《多巴胺國度》</a>這本書時，學習到了成癮的機制還有延後享樂的好處，就趁這個機會來實驗一下吧。</p><p>因為要讓爽痛平衡<sup id="fnref:2" class="footnote-ref"><a href="#fn:2" rel="footnote"><span class="hint--top hint--rounded" aria-label="大腦中爽與痛的感覺如同翹翹板，追求刺激會讓翹翹板往「爽」傾斜，同時反彈的「痛」也會加大。長期下來支點位移，導致需要越來越強的刺激才能感到快樂。詳見[《多巴胺國度》讀後心得](/2026-03-09/dopamine-nation/)。">[2]</span></a></sup>回歸正常，需要遠離刺激（對我來說就是電玩）30 天左右，加上最近太想買一顆後背包，結合這兩項的規則就變成：</p><ul><li>連續 30 天不玩任何電玩，</li><li>如果有達成可以購買想要的背包</li><li>紀錄過程中的感受</li></ul><p>其實很久之前在準備面試或讀書時就有成功戒癮，但總是會在抱持「玩一下下也無妨」的情況下再度淪陷，特別是打了一個晚上都贏不了的英雄聯盟。</p><p>這次在過程中將注意力轉去從事閱讀、嘗試寫作、修照片甚至是和小孩一起倒頭就睡。轉眼間一個月就過去了，過程中有注意到幾個身體向外尋求多巴胺的信號：</p><ol><li>想要吃一堆垃圾食物，越油越鹹越好</li><li>想狂滑蝦皮或是淘寶等購物平台</li></ol><p>這些狀況大概過了兩週後就好轉了，給想要戒除自己「毒品」的每個人一點鼓勵，撐下去就好！</p><p>現在可以自由安排自己的 Me Time，看是要閱讀還是學習都可以，累了就直接去睡覺，這樣的晚上也挺不錯的。</p><h3 id="註解"><a href="#註解" class="headerlink" title="註解"></a>註解</h3><section class="footnotes"><div class="footnote-list"><ol><li><span id="fn:1" class="footnote-text"><span>大腦放空、自動導航，不用思考地重複同樣行為。<a href="#fnref:1" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:2" class="footnote-text"><span>大腦中爽與痛的感覺如同翹翹板，追求刺激會讓翹翹板往「爽」傾斜，同時反彈的「痛」也會加大。長期下來支點位移，導致需要越來越強的刺激才能感到快樂。詳見<a href="/2026-03-09/dopamine-nation/">《多巴胺國度》讀後心得</a>。<a href="#fnref:2" rev="footnote" class="footnote-backref"> ↩</a></span></span></li></ol></div></section>]]>
      </content:encoded>
    </item>
    <item>
      <title>【逆行人生】讀後心得</title>
      <link>https://weiblog.me/2026-03-15/against-the-current/</link>
      <description>因為朋友推薦而開始閱讀這本可能沒這麼熱門的好書，強化了我閱讀和寫作的動力</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E8%AE%80%E6%9B%B8%E5%BF%83%E5%BE%97/">讀書心得</category>
      <category domain="https://weiblog.me/tags/%E9%96%B1%E8%AE%80%E5%8A%9B/">閱讀力</category>
      <category domain="https://weiblog.me/tags/%E5%AF%AB%E4%BD%9C/">寫作</category>
      <category domain="https://weiblog.me/tags/%E6%88%90%E5%8A%9F/">成功</category>
      <enclosure url="https://weiblog.me/2026-03-15/against-the-current/image-0.png"/>
      <pubDate>Sat, 14 Mar 2026 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-03-15/against-the-current/image-0.png" class=""><div style="margin-bottom: 20px"></div><h2 id="關於這本書"><a href="#關於這本書" class="headerlink" title="關於這本書"></a>關於這本書</h2><p>逆行人生這本書是韓國一位 30 歲實現財務自由退休的作者自青寫的，書中介紹了他如何從一位外貌、收入、課業都不起眼的年輕人，蛻變成擁有諸多事業的創業家。</p><p>作者提出了一個觀念，把過人生的方法分為兩類：</p><ol><li>順理人生者： 依循命運規劃，走上理所當然之路的人，和無路之路中保羅提到的「跳圈者」有異曲同工之妙。</li><li>逆行人生者： 違背命運，靠自己力量另闢路徑的人。</li></ol><p>而這本書，就是作者給每一個想成為逆行人生者的人，最具體的行動指南。</p><h2 id="如果只能畫一條重點"><a href="#如果只能畫一條重點" class="headerlink" title="如果只能畫一條重點"></a>如果只能畫一條重點</h2><blockquote><p>我認為我人生中做的最好的一點是實踐了「22策略」</p></blockquote><p>作者認為人生要成功、達成財富自由是有攻略的，因此他根據自己從谷底翻身的經驗，為逆行人生者整理出七個人生升級的階段，從自我意識的解體，一步步走向財務自由，但所有步驟方法都是圍繞著兩件事在進行： 「閱讀」和「寫作」。</p><p>作者自己每天堅持會花兩小時閱讀以及寫作，這就是有名的「22策略」。</p><p>我認為作者的觀念非常簡單：</p><figure class="highlight clean"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs clean">閱讀 + 寫作 -&gt; 大腦升級 -&gt; 有了各種解決問題的能力 -&gt; 邁向成功<br></code></pre></td></tr></table></figure><p>假設整本書讀完內容全忘光了，但只要記得持續做到 Input 和 Output，就能夠獲得解決問題的能力，在未知的人生中不再感到害怕。</p><h2 id="和我的關係"><a href="#和我的關係" class="headerlink" title="和我的關係"></a>和我的關係</h2><p>老實說在 AI 的浪潮下，身為軟體工程師我感到非常害怕，害怕有天跟不上變化。但自青的這本書給了我一個努力的方向，讓我看到了不同的可能。「只要持續做到閱讀和寫作，就算是我也能獲得解決問題的能力吧」，我心理是這樣想的，希望能持續透過寫自己的部落格來訓練思考。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>注意力是最重要的資產，你不在乎就會被整碗端走</title>
      <link>https://weiblog.me/2026-03-13/attention-is-the-most-valuable-asset/</link>
      <description>在這個資訊爆炸的時代，注意力已成為最稀缺的資源。科技公司、媒體、廣告商都在搶奪你的注意力，而你卻渾然不覺</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/%E6%B3%A8%E6%84%8F%E5%8A%9B/">注意力</category>
      <category domain="https://weiblog.me/tags/%E6%99%82%E9%96%93%E7%AE%A1%E7%90%86/">時間管理</category>
      <category domain="https://weiblog.me/tags/%E7%94%9F%E6%B4%BB%E6%80%9D%E8%80%83/">生活思考</category>
      <enclosure url="https://weiblog.me/2026-03-13/attention-is-the-most-valuable-asset/image-0.png"/>
      <pubDate>Fri, 13 Mar 2026 04:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-03-13/attention-is-the-most-valuable-asset/image-0.png" class=""><div style="margin-bottom: 20px"></div><p>我們可能都聽過「注意力經濟」這個詞，可以說在這個時代誰掌握了注意力就能獲得巨大的財富。</p><p>下班了在通勤的火車上，用 Facebook 犒賞自己，滑過一則一則貼文，偶爾看看跳出來的愚蠢影片，不一會兒就回到家了。</p><p>用餐完，把孩子撂倒後終於迎來自己的小確幸時間，打開 Instagram 看看喜歡的攝影師有沒有新的作品，「喔！這則 Reels 好有趣他怎麼拍攝的？」… 回頭看時間已經一點了。</p><p>這就是每天發生在我身上的事情，白天總記掛著許多想做的事情，但回到家就把所有想法拋諸腦後。</p><p>但這其實不是我的問題，許多公司正是利用「成癮性」來綁架我們的注意力，而目的就是要透過大量的廣告獲得利益。這種系統不勝枚舉，舉凡讓人滑不停的社群平台、一直推薦優質好物的電商、贏或輸都會想來下一局的遊戲等等。</p><p>這難道不是我自己的選擇嗎？</p><p>直到某天坐在書桌前讀《多巴胺國度》，讀到每個人都有自己的「毒品」這件事，我愣了一下——原來我以為的「選擇」，只是大腦在追求多巴胺。</p><p>當經過精挑細選的貼文、短影音充斥在我眼前時，就不會注意到那些被冷落在一旁「真正想做的事」了。</p><p>因為這個時代注意力是一項很重要的資產，沒有好好留意，就會被偷走。你還記得上次在心中想著「總有一天我要學會…」嗎？</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>【多巴胺國度】讀後心得</title>
      <link>https://weiblog.me/2026-03-09/dopamine-nation/</link>
      <description>
        <![CDATA[<img src="/2026-03-09/dopamine-nation/image-0.jpg" class="">
<div style="margin-bottom: 20px"></div>

<h2 id="關於這本書"><a href="#關於這本書" class=]]>
      </description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E8%AE%80%E6%9B%B8%E5%BF%83%E5%BE%97/">讀書心得</category>
      <category domain="https://weiblog.me/tags/%E9%96%B1%E8%AE%80%E5%8A%9B/">閱讀力</category>
      <category domain="https://weiblog.me/tags/%E6%B3%A8%E6%84%8F%E5%8A%9B/">注意力</category>
      <category domain="https://weiblog.me/tags/%E7%9F%A5%E8%AD%98%E7%AE%A1%E7%90%86/">知識管理</category>
      <enclosure url="https://weiblog.me/2026-03-09/dopamine-nation/image-0.jpg"/>
      <pubDate>Mon, 09 Mar 2026 14:35:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-03-09/dopamine-nation/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><h2 id="關於這本書"><a href="#關於這本書" class="headerlink" title="關於這本書"></a>關於這本書</h2><p>《多巴胺國度》(Dopamine Nation) 的作者是史丹佛大學醫學院的精神科教授安娜・蘭布克 (Anna Lembke)。安娜在這本書中透過和病患的對談，解釋了各式各樣的上癮狀況，並提供了一些治療的建議。<br>最重要的是，本書深入探討了「多巴胺」這個神經傳導物質如何影響我們的快樂與痛苦，以及大腦中「爽痛平衡」的機制。</p><h2 id="如果只能畫一條重點"><a href="#如果只能畫一條重點" class="headerlink" title="如果只能畫一條重點"></a>如果只能畫一條重點</h2><blockquote><p>「愉悅與痛覺的處理現場在大腦中有重疊的地方，且這兩者知覺的運作是透過一種拮抗機制。用白話講，就是爽與痛存在彼此消長的關係。」</p></blockquote><p>上面在講的就是<strong>爽痛平衡 (Pleasure and Pain Balance)</strong>。在腦中，爽與痛的感覺如同在玩一座翹翹板，在「爽」的這側下壓我們就會感到快樂，下壓的力道越大我們就越快樂。但同時，身體的平衡機制會對翹翹板的另一端「痛」下壓：這就是我們爽快地打完電動、吃完油膩炸雞後，會產生欲求不滿甚至空虛感的原因。<br>而大腦為了緩解這個空虛，會繼續鼓勵我們追求更多的多巴胺來往「爽」的方向壓，於是我們的遊戲一場接一場、垃圾食物一盤接一盤。</p><p>漸漸地，翹翹板開始對「爽」產生耐受性，亦即同樣的刺激已經沒辦法在「爽」這端下壓了。作者安娜用了一個生動的比喻：<strong>支點位移了</strong>。<br>當支點往「爽」這端靠近時，力臂變短，且相對的「痛」那端力臂變長了。這時生活中只要有一點不愉快在「痛」那端下壓，就能讓人感到巨大的痛苦與憂鬱——這就是現代人為什麼普遍不快樂的原因。</p><p>但這個機制還沒完，因為痛苦會促使人們追求更多「爽」的刺激來逃避，進而陷入了一個無窮迴圈，這就是成癮的機制。</p><p>好消息是，只要我們透過<strong>隔絕「爽」的刺激</strong>來打破這個迴圈，就能讓翹翹板自動回到當初平衡的狀態。回到平衡的狀態有許多好處：其中一個是只要一點微小的刺激就能獲得幸福感與快樂，同時也不用承受強烈的「痛」的反噬，讓我們在平常的生活中找到平靜及喜樂。</p><h2 id="和我的關係"><a href="#和我的關係" class="headerlink" title="和我的關係"></a>和我的關係</h2><p>過去上課被教導多巴胺會帶來快樂和幸福感，但讀完這本書才知道，多巴胺只是一個神經元間的傳導物質。它會刺激腦中的獎勵路徑，讓我們在「期待」的過程中感到興奮。這完美驗證了為什麼每次下訂東西前都會很興奮，但是這種興奮感在開箱後的一天、兩天內就會迅速消散。</p><p>意識到自己沉迷於電玩，並浪費了許多寶貴的自由時間後，我決定讓大腦的平衡回到正常的狀態。我馬上把 PC 中的遊戲都刪了，展開為期 30 天的「自縛」療程，並重拾閱讀、寫作等興趣來填補這中間多巴胺的落差。</p><p>同時，了解這個原理後，我也能更客觀地檢視自己的購物上癮習慣。我現在學會透過「延遲享樂」來平衡大腦，例如把最近想買的新玩具當成是 30 天自縛成功的獎勵——不僅找回了平靜，也讓獲得獎勵時的快樂更加純粹，一舉兩得。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>終於買了網域名</title>
      <link>https://weiblog.me/2026-03-08/buying-domain-name/</link>
      <description>連買域名都能衝動購物，買完後悔的嗎？！</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/%E7%B6%B2%E5%9F%9F/">網域</category>
      <category domain="https://weiblog.me/tags/Cloudflare/">Cloudflare</category>
      <enclosure url="https://weiblog.me/2026-03-08/buying-domain-name/image-0.jpg"/>
      <pubDate>Sun, 08 Mar 2026 12:20:35 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-03-08/buying-domain-name/image-0.jpg" class=""><div style="margin-bottom: 20px"></div><h1 id="終於買了網域名"><a href="#終於買了網域名" class="headerlink" title="終於買了網域名"></a>終於買了網域名</h1><p>最近剛把網誌從 GitHub Pages 搬到 Cloudflare Pages 上，發現 Cloudflare 提供了不少好用的功能，像是自訂網域、SSL、CDN 等等甚至連 Web Analytics 都有，而且後台介面操作也很直覺，就順手把網域也一起買了（之前都習慣在 Gandi 買）。</p><p>挑了很久，喜歡的太貴，便宜的又不符合需求，尋尋覓覓終於找到一個價錢不錯又有相關性的。</p><p>開心的下單後才發現，不對啊，如果網域名直接帶了 blog 字串，那子網域的彈性就少很多 (weiblog.me)，買都買了，這一年就加減先用吧。</p><p>我是一個很容易衝動購物的人，但我沒想到連買域名都能後悔。</p><h2 id="網域小科普：為什麼帶有特定字眼的網域不夠泛用？"><a href="#網域小科普：為什麼帶有特定字眼的網域不夠泛用？" class="headerlink" title="網域小科普：為什麼帶有特定字眼的網域不夠泛用？"></a>網域小科普：為什麼帶有特定字眼的網域不夠泛用？</h2><p>前面提到我買了 <code>weiblog.me</code> 後突然有點後悔，原因在於這會限縮未來擴充子網域（Subdomain）時的彈性。</p><p>在購買網域時，如果我們買了一個比較中性、沒有特定含義的主網域（例如 <code>wei.me</code>），未來就可以依照不同服務拆分出很多直覺的子網域配置，例如：</p><ul><li><code>blog.wei.me</code> (用來放部落格)</li><li><code>api.wei.me</code> (用來放後端 API 服務)</li><li><code>shop.wei.me</code> (用來放個人電商)</li><li><code>portfolio.wei.me</code> (用來放作品集)</li></ul><p>但當我的主網域直接包含了 <code>blog</code> 這個字串（變成 <code>weiblog.me</code>），未來如果要架設非部落格的服務，子網域配著唸起來就會變得很奇怪：</p><ul><li><code>api.weiblog.me</code> (部落格的 API 嗎？)</li><li><code>shop.weiblog.me</code> (在部落格周邊商店？)</li></ul><p>如果域名帶有特定用途的字眼，就很容易被綁死。在選購網域時，最好先思考一下未來有沒有擴充其他類型服務的可能。如果只打算拿來寫文章，那買帶有 blog 的網域當然沒問題；但如果考慮到未來想在同一個主網域下掛載各式各樣的 side project 或服務，一個乾淨、中性的名字會是更好的選擇。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>OAuth 2.0 學習筆記</title>
      <link>https://weiblog.me/2026-03-07/oauth2-learning-notes/</link>
      <description>OAuth 2.0 是一個開放標準的授權協定，主要用來解決如何在「不給出帳號密碼」的情況下，安全地讓第三方應用程式存取使用者在某個服務上的受保護資源</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/OAuth-2-0/">OAuth 2.0</category>
      <category domain="https://weiblog.me/tags/%E8%B3%87%E5%AE%89/">資安</category>
      <category domain="https://weiblog.me/tags/%E6%8E%88%E6%AC%8A/">授權</category>
      <enclosure url="https://weiblog.me/2026-03-07/oauth2-learning-notes/image-0.png"/>
      <pubDate>Sat, 07 Mar 2026 07:08:15 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-03-07/oauth2-learning-notes/image-0.png" class=""><div style="margin-bottom: 20px"></div><h2 id="OAuth-2-0-的四種核心角色"><a href="#OAuth-2-0-的四種核心角色" class="headerlink" title="OAuth 2.0 的四種核心角色"></a>OAuth 2.0 的四種核心角色</h2><p>在了解 OAuth 2.0 的運作之前，我們需要先認識參與其中的四個關鍵角色：</p><ol><li><strong>Resource Owner (資源擁有者)</strong>：通常就是指「使用者本人」。使用者擁有那些客戶端準備代為存取的受保護資源。</li><li><strong>Resource Server (資源伺服器)</strong>：實際裝載並保管使用者受保護資源的地方。</li><li><strong>Client (用戶端 &#x2F; 第三方應用程式)</strong>：代替使用者去存取受保護資源的應用程式。</li><li><strong>Authorization Server (授權伺服器)</strong>：負責驗證使用者的身分與授權意願，並核發 Token 給第三方應用程式的伺服器。</li></ol><h2 id="OAuth-2-0-的基本運作流程"><a href="#OAuth-2-0-的基本運作流程" class="headerlink" title="OAuth 2.0 的基本運作流程"></a>OAuth 2.0 的基本運作流程</h2><p>OAuth 的核心精神在於透過授權碼與 Token 交換權限，一個最標準的基本流程可以拆解為以下四個步驟：</p><ul><li><strong>步驟 A</strong>：使用者在 Authorization Server 進行登入，並同意授權給 Client。</li><li><strong>步驟 B</strong>：Authorization Server 確認授權後，回傳一組授權碼 (Authorization Code) 給 Client。</li><li><strong>步驟 C</strong>：Client 拿著這組授權碼，去向 Authorization Server 換取 Access Token (存取權杖)。</li><li><strong>步驟 D</strong>：Client 拿著 Access Token，正式向 Resource Server 請求並取得受保護的資料。</li></ul><h2 id="四種常見的授權模式-Authorization-Grants"><a href="#四種常見的授權模式-Authorization-Grants" class="headerlink" title="四種常見的授權模式 (Authorization Grants)"></a>四種常見的授權模式 (Authorization Grants)</h2><p>根據不同的應用場景需求，OAuth 2.0 定義了四種授權模式。這些模式實際上都是基於上述的「基本流程」進行適當的微調或刪減而來：</p><ol><li><strong>Authorization Code (授權碼模式)</strong></li><li><strong>Implicit (隱式授權模式)</strong></li><li><strong>Client Credentials (用戶端憑證模式)</strong></li><li><strong>Resource Owner Password Credentials (使用者密碼模式)</strong></li></ol><h3 id="1-Authorization-Code-授權碼模式"><a href="#1-Authorization-Code-授權碼模式" class="headerlink" title="1. Authorization Code (授權碼模式)"></a>1. Authorization Code (授權碼模式)</h3><p>這是最常見、也是<strong>安全性最高</strong>的模式。它的流程就是完整走完上述基本流程的 A 到 D 步驟。</p><p>在這個模式中，回傳的「授權碼」本身並不能直接用來存取資源，而且它的有效期限極短。Client 必須在後端伺服器拿著這個授權碼去 Authorization Server 換取 Access Token，才能真正去存取目標資源。這樣的設計能確保 Access Token 不會在瀏覽器或前端外洩。</p><p>下面來看一個我們最常見的例子：</p><p><img src="/2026-03-07/oauth2-learning-notes/image-1.svg" alt="OAuth 2.0 Authorization Code Flow"></p><h3 id="2-Implicit-Grant-隱式授權模式"><a href="#2-Implicit-Grant-隱式授權模式" class="headerlink" title="2. Implicit Grant (隱式授權模式)"></a>2. Implicit Grant (隱式授權模式)</h3><p>這個模式<strong>跳過了「用授權碼換 Access Token」的步驟 (即跳過步驟 C)</strong>。</p><p>當使用者同意授權後，Authorization Server 會直接把 Access Token 放在網址中回傳。這個模式通常應用於沒有後端的 SPA (Single-Page Application) 單頁式應用程式，讓前端可以直接拿著 Access Token 去呼叫 API。</p><p>因為 Access Token 在交換過程中會直接暴露在 URL 中，這個方法非常不安全，在現代的開發中已經不被推薦使用。甚至在 OAuth 2.1 中已經被棄用。</p><h3 id="3-Client-Credentials-用戶端憑證模式"><a href="#3-Client-Credentials-用戶端憑證模式" class="headerlink" title="3. Client Credentials (用戶端憑證模式)"></a>3. Client Credentials (用戶端憑證模式)</h3><p>這個模式<strong>跳過了需要使用者參與的步驟 A 與 B</strong>。</p><p>在這種情境下，Client 自己本身就是資源擁有者。Client 擁有一組自己的 Secret Key，它可以直接拿著這組 Key 去向 Authorization Server 證明自己的身分，並直接換取 Access Token。通常用於內部微服務或伺服器對伺服器 (M2M) 的 API 溝通。</p><h3 id="4-Resource-Owner-Password-Credentials-使用者密碼模式"><a href="#4-Resource-Owner-Password-Credentials-使用者密碼模式" class="headerlink" title="4. Resource Owner Password Credentials (使用者密碼模式)"></a>4. Resource Owner Password Credentials (使用者密碼模式)</h3><p>這個模式同樣<strong>跳過了步驟 A 與 B</strong>。</p><p>作法是讓使用者直接把帳號密碼交給 Client，然後讓 Client 在步驟 C 拿著使用者的帳號密碼去向 Authorization Server 取得 Access Token。</p><p>這個作法也就是把密碼完全交付給了第三方，<strong>極度不安全</strong>，所以只能用在「絕對可信任」的 Client 上，例如作業系統內建的 App 或公司自家的內部應用系統。</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>OAuth 2.0 是一個規範，主要用來解決如何在不給出密碼的情況下，安全地讓第三方應用程式可以存取使用者在某個服務上的資源。共有四種不同模式，其中最常被應用也是最安全的為授權碼模式。</p><p>最後補充一點，包含我自己，很多人會把 OAuth 和 SSO 搞混，以為看到授權頁面就是在做登入。這其實只對了一半：SSO 通常會搭配 OIDC (OpenID Connect) 來達到「認證 (Authentication)」及「授權 (Authorization)」，而 OAuth 這個機制本身，嚴格來說只負責處理**「授權」**的部分，像是授權第三方應用存取使用者的 FB 大頭照、信箱等等。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ol><li><a href="https://datatracker.ietf.org/doc/html/rfc6749#section-1.1">RFC6749</a></li><li><a href="https://learn.microsoft.com/zh-tw/entra/identity-platform/v2-protocols">Microsoft Learn</a></li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>彩色電子閱讀器真的適合我嗎？</title>
      <link>https://weiblog.me/2026-03-04/is-color-ereader-really-suitable-for-me/</link>
      <description>手癢買了彩色的 Kobo 閱讀器，但用沒多久才發現我根本無法閱讀下去？！</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/%E9%96%B1%E8%AE%80%E5%99%A8/">閱讀器</category>
      <category domain="https://weiblog.me/tags/%E5%BF%83%E5%BE%97/">心得</category>
      <category domain="https://weiblog.me/tags/Kobo/">Kobo</category>
      <enclosure url="https://weiblog.me/2026-03-04/is-color-ereader-really-suitable-for-me/image-0.png"/>
      <pubDate>Wed, 04 Mar 2026 15:10:59 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-03-04/is-color-ereader-really-suitable-for-me/image-0.png" class=""><div style="margin-bottom: 20px"></div><h3 id="閱讀、閱讀、閱讀"><a href="#閱讀、閱讀、閱讀" class="headerlink" title="閱讀、閱讀、閱讀"></a>閱讀、閱讀、閱讀</h3><p>目前的閱讀習慣是紙本書和電子書雙管齊下。能帶紙本就帶紙本，出門則拎著 Kobo Sage，利用零碎時間繼續閱讀。在看了《逆行人生》這本書後，我為自己建立了一個「只要有空檔就閱讀」的習慣，期望能放大輸入的資訊量。</p><p>於是我開始物色可以放進口袋的電子閱讀器。正好 Kobo 就有一台 Clara —— 六吋的螢幕、可一手掌握，還能輕鬆塞進口袋，各方面都滿足我的需求；而且它有漂亮的白色版，還是彩色的。下訂的當下沒想太多，單純覺得彩色螢幕用起來應該只會更爽吧。</p><h3 id="Clara-Colour-vs-Sage"><a href="#Clara-Colour-vs-Sage" class="headerlink" title="Clara Colour vs Sage"></a>Clara Colour vs Sage</h3><p>在網路上下訂後，很快就拿到實機了。拆封、開機、登入帳號，我熟練地操作著 Clara Colour，將正在閱讀的書都準備好。快速瀏覽了幾頁，使用上很順暢，UI&#x2F;UX 和手上的 Kobo Sage 相同，主要只差在螢幕大小和有無實體按鍵。讓人印象深刻的是，彩色的書封非常賞心悅目。</p><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 70%">    <img src="/2026-03-04/is-color-ereader-really-suitable-for-me/image-1.jpg" class="">  </div></div><p>快速地和我的 Kobo Sage 作個比較：</p><ul><li><strong>螢幕與尺寸</strong>：Clara 是六吋彩色，Sage 是八吋黑白。</li><li><strong>手感與操作</strong>：Clara 裸機的握感非常好，翻頁主要透過點擊和滑動；Sage 則有實體按鍵，操作起來非常方便。</li><li><strong>螢幕設計</strong>：Sage 的螢幕和機身是全平面的，手指滑動到邊界時沒有段落感；而 Clara 的螢幕則是稍微下凹的。除了手感差異，還有美觀考量 - 同樣是六吋的機器，我覺得 Kindle Paperwhite 的全平面設計更勝一籌。</li><li><strong>額外功能</strong>：Sage 支援觸控筆寫字劃線，Clara 則無此功能。</li><li><strong>亮度與對比</strong>：Clara Colour 的彩色螢幕亮度偏暗，即使在陽光下也必須開啟背光才能閱讀；相比之下，黑白的 Sage 在戶外陽光下對比度極高，字體清晰，讀起來非常舒服。</li></ul><h3 id="我真的讀不下去了"><a href="#我真的讀不下去了" class="headerlink" title="我真的讀不下去了"></a>我真的讀不下去了</h3><p>然而，就在我看了大約五分鐘後，眼睛開始感到刺痛，竟然連一頁都無法再看下去。起初我以為是今天太累或用眼過度，便先去吃飯、洗澡，稍作休息後再次坐回書桌前閱讀。但同樣看了幾頁，又開始感到頭暈和眼睛痠痛。</p><p>我開始懷疑是不是自己身體出了什麼狀況，於是跑到 Reddit 和臉書社團爬文。後來才得知，彩色螢幕因為多了一層彩色濾光片，不僅會降低螢幕亮度，也會讓文字的對比度下降。在我的眼裡，Clara Colour 的文字看起來真的是模糊的，越看越疲勞、越看越刺痛。我試過網友提供的各種方法：調整色溫、改變字型、調整字體間距等，但還是無法讓我連續閱讀超過三分鐘。</p><p>最後只好忍痛退貨，並重新訂了一台黑白版的 Clara BW。</p><h3 id="回歸純粹：Clara-BW"><a href="#回歸純粹：Clara-BW" class="headerlink" title="回歸純粹：Clara BW"></a>回歸純粹：Clara BW</h3><p>收到 Clara BW 後，可以說沒有什麼新鮮感，因為這完全符合我的預期，有一種「老朋友回歸」的踏實感。輕巧的手感、高對比度的清晰顯示，丟進口袋就能隨時帶出門。</p><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 70%">    <img src="/2026-03-04/is-color-ereader-really-suitable-for-me/image-2.jpg" class="">  </div></div><br/><p>其他同事使用 Kobo Libra Colour 都非常開心，整天閱讀下來也沒有出現像我這樣的症狀，看來單純是我的眼睛與 Kobo 的彩色螢幕不對盤。</p><p>針對我目前閱讀純文字書籍的需求來說，黑白螢幕還是最合適的選擇，看一整天也不容易累。至於彩色閱讀器只好期待電子墨水技術能持續進步，希望未來有一天，我也能入手一台漂亮白色 Kobo 閱讀器。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>躺平的經濟學：當電玩的所得彈性超越了勞動的邊際回報</title>
      <link>https://weiblog.me/2026-03-03/lying-flat-economics/</link>
      <description>
        <![CDATA[<div style="margin-bottom: 10px">
  <img src="/2026-03-03/lying-flat-economics/image-0.png" class="">
</div>


<p>有沒有想過，為什麼現在越來越多年輕人選擇躺平？除了大]]>
      </description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/AI/">AI</category>
      <category domain="https://weiblog.me/tags/%E7%B6%93%E6%BF%9F/">經濟</category>
      <category domain="https://weiblog.me/tags/%E8%BA%BA%E5%B9%B3/">躺平</category>
      <category domain="https://weiblog.me/tags/%E4%BC%91%E9%96%92%E5%A5%A2%E4%BE%88%E5%93%81/">休閒奢侈品</category>
      <enclosure url="https://weiblog.me/2026-03-03/lying-flat-economics/image-0.png"/>
      <pubDate>Mon, 02 Mar 2026 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<div style="margin-bottom: 10px">  <img src="/2026-03-03/lying-flat-economics/image-0.png" class=""></div><p>有沒有想過，為什麼現在越來越多年輕人選擇躺平？除了大環境的變化，科技的進步可能也是其中一個推手。</p><h3 id="休閒奢侈品"><a href="#休閒奢侈品" class="headerlink" title="休閒奢侈品"></a>休閒奢侈品</h3><p>Mark Aguiar 在 2020 發表的論文，裡面提到在美國 21 ~ 30 歲的年輕人在過去十幾年來的每週勞動時數有下降的趨勢，同時增加的休閒時間以及花費在電玩的時間逐年增高。</p><p>Aguiar 等人從數據中得出電玩具有「休閒奢侈品」的特性，每當休閒時間多出 1%，年輕人花費在電腦或電玩上的時間就會多出 2.48%，這在經濟學上稱為所得彈性大於 1，意即當你擁有更多資源——也就是休閒時間時，你會不成比例地投入更多在上面。</p><p>成為休閒奢侈品意味著電玩的邊際效益遞減的很慢，意味著連續打電動五小時可能都還是能獲得相同的滿足感。年輕人會將多出來的休閒時間持續投入在電玩上，而不是想辦法從工作中獲得滿足感。現在演算法與各種遊戲機制能夠讓我們的大腦追求更多多巴胺，科技產物牢牢抓住我們的注意力與時間，因此年輕人寧可拿時間去打電動或滑社群媒體，也不願意去加班或爭取升遷。</p><h3 id="未來的挑戰：AI-時代的休閒盈餘"><a href="#未來的挑戰：AI-時代的休閒盈餘" class="headerlink" title="未來的挑戰：AI 時代的休閒盈餘"></a>未來的挑戰：AI 時代的休閒盈餘</h3><p>我們現在正面臨 AI 帶來的工作模式變化，未來如果因為生產力大幅提升，所有人都獲得了更多的休閒時間，是否會有更多的休閒奢侈品產生呢？</p><p>當我們有更多時間時，我們會用來創造更有價值的生命體驗，還是會集體沈溺在邊際效益遞減極慢的數位黑洞中？這或許是一個值得我們深思的問題。</p><h3 id="參考資料"><a href="#參考資料" class="headerlink" title="參考資料"></a>參考資料</h3><ol><li>Leisure Luxuries and the Labor Supply of Young Men(<a href="https://www.markaguiar.com/citation/leisure-luxuries">https://www.markaguiar.com/citation/leisure-luxuries</a>)</li></ol><h2 id="參考資料-1"><a href="#參考資料-1" class="headerlink" title="參考資料"></a>參考資料</h2><ol><li>Aguiar, Mark. 2020. “Leisure Luxuries and the Labor Supply of Young Men.” Accessed March 4, 2026. <a href="https://www.markaguiar.com/citation/leisure-luxuries">https://www.markaguiar.com/citation/leisure-luxuries</a></li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>腦中毫無想法是因為 Input 太少</title>
      <link>https://weiblog.me/2026-02-22/brain-automation-and-input/</link>
      <description>大腦自動化需要不斷的資訊輸入刺激，探討如何通過有效的信息獲取來驅動思考能力</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E9%9A%A8%E7%AD%86/">隨筆</category>
      <category domain="https://weiblog.me/tags/%E6%95%A3%E6%BC%AB%E5%8A%9B/">散漫力</category>
      <category domain="https://weiblog.me/tags/%E8%BC%B8%E5%85%A5%E8%88%87%E8%BC%B8%E5%87%BA/">輸入與輸出</category>
      <category domain="https://weiblog.me/tags/%E7%9F%A5%E8%AD%98%E7%AE%A1%E7%90%86/">知識管理</category>
      <category domain="https://weiblog.me/tags/%E8%87%AA%E5%8B%95%E5%8C%96%E5%B7%A5%E5%85%B7/">自動化工具</category>
      <enclosure url="https://weiblog.me/2026-02-22/brain-automation-and-input/image-0.png"/>
      <pubDate>Sun, 22 Feb 2026 08:56:11 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-02-22/brain-automation-and-input/image-0.png" class=""><div style="margin-bottom: 20px"></div><p>我平時都會有想要學習或研究的東西以及各種不同的想法，但是過去一段時間覺察到自己變成了一個無聊、停止思考的人。</p><p>某天突然領悟到這是因為資訊的 Input 太少，當我在 FB 技術社團看到一些應用，發現腦袋開始轉起來了：「家裡剛好有一台閒置的 Mac Mini，是不是可以拿來養龍蝦？」「有了龍蝦後是不是可以幫我自動過濾 FB 上對我有價值的資訊並推播給我」一連串的想法接踵而來。</p><p>在「逆行人生」這本書中作者提到了「大腦自動化」這個概念。當大腦被不斷的訓練後，即使沒有在學習也能夠無時無刻的進行思考，要訓練大腦就要有 Input，因此大量接收資訊以及閱讀就是最好的方法。</p><p>雖然現在是個資訊爆炸的時代，但不是所有資訊都是有意義的。如果只是被動的接受「推式資訊」，光是過濾演算法給出的垃圾就已經筋疲力盡了。閱讀書籍當然是一個最簡單的方法，但我還是要找到一個方法來大量獲得有益的網路資訊。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>用 Google Apps Script 來作一個 Line Bot 如何</title>
      <link>https://weiblog.me/2026-01-10/build-line-bot-with-gas/</link>
      <description>最近想做個 side project 來搞個家庭記帳軟體，但是又覽著搞部屬那些，和 AI 討論後決定用 Google Apps Script 來寫一個...</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/Google-Apps-Script/">Google Apps Script</category>
      <category domain="https://weiblog.me/tags/Line-Bot/">Line Bot</category>
      <category domain="https://weiblog.me/tags/Clasp/">Clasp</category>
      <category domain="https://weiblog.me/tags/Webhook/">Webhook</category>
      <category domain="https://weiblog.me/tags/timeout/">timeout</category>
      <enclosure url="https://weiblog.me/2026-01-10/build-line-bot-with-gas/image-0.png"/>
      <pubDate>Sat, 10 Jan 2026 02:48:54 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2026-01-10/build-line-bot-with-gas/image-0.png" class=""><div style="margin-bottom: 20px"></div><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近看完書領悟到了一點 「要解決財務焦慮的第一步，就是要知道家庭支出」，於是開始研究了一下市面上的可以協作的記帳軟體，但不是要付費就是功能太複雜，最後決定自己開發一個來試試看。</p><h2 id="目標與設計"><a href="#目標與設計" class="headerlink" title="目標與設計"></a>目標與設計</h2><p>我目前想要的功能很簡單，就是有個介面可以輸入記帳項目，然後可以把交易記錄到 Google Sheet 上就好，反正要整理分析最後人工來作也不會花上太多時間。</p><p>想到之前一直想要來玩玩看 Google Apps Script (以下都簡稱 GAS)，加上 GAS 又能無縫串接 Google Sheet，正好符合這次的需求。</p><p>初步架構就決定是這樣了:</p><div style="display: flex; justify-content: space-evenly">  <div style="padding: 5px; width: 40%">    <img src="/2026-01-10/build-line-bot-with-gas/image-1.png" class="">  </div></div><h2 id="開發過程"><a href="#開發過程" class="headerlink" title="開發過程"></a>開發過程</h2><p>全程 Vibe coding 都讓 AI 寫，我只負責測試以及驗收功能，還有設定 Line Channel 這些後台設定而已。</p><p>GAS 其實就是一個 PaaS 服務，使用者只需要照自己的業務邏輯撰寫程式碼，後面的執行環境、部屬等等 GAS 都會幫你準備好，讓像我這樣的懶人可以很快的直接看到成果。難怪很多人會拿 GAS 當作軟體工程啟蒙，這比寫爬蟲什麼有趣多了。</p><p>要開發 GAS 可以直接在 GAS 的網頁上開發;或是像我一樣在自己的 IDE 上開發完，再利用 <code>clasp</code> 這個 CLI 工具推送到 GAS 上完成部屬，後者的好處是會有一份程式碼在 local，可以使用 IDE 或是 AI 工具協助開發。（相信我你不會想在 IDE 寫完程式碼，然後用複製貼上的方式去網頁版編輯器）</p><p>GAS 執行時的運作也很簡單，就是兩個入口：</p><figure class="highlight wren"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs wren"><span class="hljs-variable">function</span> <span class="hljs-title function_">doGet</span>(<span class="hljs-params">e</span>) &#123;&#125;<br><br><span class="hljs-variable">function</span> <span class="hljs-title function_">doPost</span>(<span class="hljs-params">e</span>) &#123;&#125;<br><br></code></pre></td></tr></table></figure><p>我們把需要的 callback 邏輯寫在這兩個函式中，以我的案例要接 Line Messaging API 打過來的請求，大概會長這樣：</p><figure class="highlight ada"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs ada"><span class="hljs-keyword">function</span> <span class="hljs-title">doPost</span>(e) &#123;<br>  var contents = JSON.parse(e.postData.contents);<br>  var events = contents.events;<br><br>  events.forEach(<span class="hljs-keyword">function</span>(event) &#123;<br>    <span class="hljs-keyword">if</span> (event.<span class="hljs-keyword">type</span> <span class="hljs-type">=== </span><span class="hljs-symbol">&#x27;message</span>&#x27; &amp;&amp; event.message.<span class="hljs-keyword">type</span> <span class="hljs-type">=== </span><span class="hljs-symbol">&#x27;text</span>&#x27;) &#123;<br>      var eventId = event.webhookEventId;<br>      ...<br><br>      // <span class="hljs-keyword">do</span> something<br>    &#125;<br>  &#125;);<br>&#125;<br></code></pre></td></tr></table></figure><p>要存取 Google Sheet 也很簡單，直接在程式碼中呼叫 <code>SpreadsheetApp.openById(sheetId);</code> 就可以透過 ID 取得，但是要注意後面在部屬的時候要設定執行身分，如果這個 sheet 只有本人可以存取，那執行身分就要設定為本人。</p><p>部屬也是非常簡單，只要在網頁上點選右上角的「部屬」按鈕，就能完成部屬了。</p><div style="display: flex; justify-content: space-evenly">  <div style="padding: 5px; width: 40%">    <img src="/2026-01-10/build-line-bot-with-gas/image-3.png" class="">  </div></div><p>部屬成功後會得到一個像這樣的 URL: <code>https://script.google.com/macros/s/AKfycbxOFRfffExrffffsaaa63opssssss-kffsssFRffT5Y-y8hBBBBCCC/exec</code>，這個就是我們服務的對外 URL。是的你沒看錯，連 HTTPS 都幫忙搞定了。</p><p>拿到這個後就可以把這個 URL 貼到 Line Channel 上的 webhook 欄位使用。當 Line Message API 接收到特定 Channel 的訊息後就會發送一個請求過來這個 URL。</p><p>到這邊其實就算是完成一個簡單的 Line Bot 了。</p><h2 id="遇到的問題"><a href="#遇到的問題" class="headerlink" title="遇到的問題"></a>遇到的問題</h2><h3 id="在-Line-Developer-頁面測試-webhook-都不會過"><a href="#在-Line-Developer-頁面測試-webhook-都不會過" class="headerlink" title="在 Line Developer 頁面測試 webhook 都不會過"></a>在 Line Developer 頁面測試 webhook 都不會過</h3><p>這個我真的卡了一陣，但最後找到保哥 2025 的貼文，知道Bot 還是能正常回應就不管他了。</p><div style="display: flex; justify-content: space-evenly; margin-bottom: 20px">  <div style="padding: 5px; width: 100%">    <img src="/2026-01-10/build-line-bot-with-gas/image-4.png" class="">  </div></div><div style="display: flex; justify-content: space-evenly">  <div style="padding: 5px; width: 100%">    <img src="/2026-01-10/build-line-bot-with-gas/image-5.png" class="">  </div></div><h3 id="沒有-Log-超難-debug"><a href="#沒有-Log-超難-debug" class="headerlink" title="沒有 Log 超難 debug!"></a>沒有 Log 超難 debug!</h3><p>GAS 在開啟「所有人都能呼叫」時，在執行項目那邊會看不到 log，即使呼叫的人是自己，GAS 也會把 log 隱藏。</p><p>有看到社團有人是把 log 寫到另一份 Google sheet，或是乾脆通靈 (?!)。我是為了看 log 把這個專案綁定到我的 GCP 專案中，就可以在 Cloud Logging 上看到 log 了。</p><h3 id="執行太慢，超過-Line-Messaging-API-的-1s-timeout-時間"><a href="#執行太慢，超過-Line-Messaging-API-的-1s-timeout-時間" class="headerlink" title="執行太慢，超過 Line Messaging API 的 1s timeout 時間"></a>執行太慢，超過 Line Messaging API 的 1s timeout 時間</h3><p>這個是目前唯一沒解決的問題，從 Line Messaging API server 發送過來的請求要在 1s 內回覆 <code>HTTP 200</code> 不然就會被視為失敗，觸發 Line Messaging API 的重試機制，會每隔一段時間重新發送重複的訊息 (記帳 sheet 上會多出一堆相同的紀錄)。</p><p>為了解決這個問題我也嘗試把執行時間較久的 callback 改成先建立一個 Time Trigger 在一秒後執行寫入 Google sheet 的操作。但我怎麼樣都無法把執行時間壓在一秒內，最後還是會觸發重試。</p><p>另一方面針對重試發過來的請求可以用一個 cache 阻擋他，至少讓重複的資料不要寫入 Google sheet。</p><p>這個問題最後還是沒解決，雖然一切都能正常運作，但是看到 log 中有 Line Messaging API 送過來的請求還是會很阿雜。</p><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><p>作為一個全託管服務，GAS 以他親民的使用方式讓很多非工程師也能快速自動化工作，除了 Google Sheet，GAS 也能串接其他 SaaS 服務如 Drive, Forms 甚至是 Big Query 等等，如果工作上都會用到這些服務，GAS 不失為一個自動化的選擇！</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>CKA 考試心得</title>
      <link>https://weiblog.me/2025-12-13/cka-preparation/</link>
      <description>因為考試快要過期了，CKAD 通過後馬上開始排定考試日期和進度，用一個月刷完了課程和 Lab...</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/Cloud-Native/">Cloud Native</category>
      <category domain="https://weiblog.me/tags/CKA/">CKA</category>
      <category domain="https://weiblog.me/tags/Kubernetes/">Kubernetes</category>
      <category domain="https://weiblog.me/tags/K8s/">K8s</category>
      <category domain="https://weiblog.me/tags/Certification/">Certification</category>
      <pubDate>Sat, 13 Dec 2025 08:24:27 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>CKAD 考完後，距離考試過期只剩兩個月，直接把考試日期壓在過期前一週，排定考試後就開始準備了。雖然遇到了一點意外但還是低空飛過。</p><p>再次附上人權</p><div style="display: flex; justify-content: space-evenly">  <div style="padding: 5px; width: 80%">    <img src="/2025-12-13/cka-preparation/image-1.png" class="">  </div></div><h2 id="準備過程"><a href="#準備過程" class="headerlink" title="準備過程"></a>準備過程</h2><p>原本排定用六週準備，花四週刷完課程用兩週狂刷題，但<del>計畫總是趕不上變化</del>咆嘯深淵新模式太好玩了，實際上認真的時間更少。分享一下用到的資源：</p><ol><li><a href="https://kubernetes.io/">Kubernetes 官方文件</a> - 還是要看熟，至少知道要找某個文件要下什麼關鍵字。</li><li><a href="https://www.udemy.com/course/certified-kubernetes-administrator-with-practice-tests/overview/?udfrontends=true">Udemy 課程</a> - 附 KodeKloud Lab 可以大量練習</li><li><a href="https://killercoda.com/killer-shell-cka">Killercoda</a> - Scenario 超多，閒來無事就刷一提練練手感。</li><li>報名考試送的兩次 killer.sh 模擬考</li></ol><p>我的準備方式是上 Udemy 的課程，邊上課邊做 Lab，有問題馬上查官方文件並記下關鍵字。課程因為基礎的部分和 CKAD 幾乎重複，我多半只是做完 Lab（甚至沒做完）就往下看。</p><p>刷完課程做的三個 mock exam 和 challenge 後，就來刷 Killercoda 的各種練習題。考前一週刷 Killer.sh 的模擬考，模擬考的題目和正式考試很接近，做完務必每題都深入檢討。</p><h2 id="心得"><a href="#心得" class="headerlink" title="心得"></a>心得</h2><p>準備的過程其實還滿快樂的（<del>如果沒有時間壓力就更好了</del>）。和 CKAD 不同，CKA 著重在 control plane 的元件以及 cluster 的設定，也會考修復一個壞掉的 cluster。中間也會穿插一些 CKAD 範圍的簡單題目，例如修改 deployment、建立 Ingress 等等。</p><p>第一次考試做到大概第十題後，開始不能複製題目給出的資訊；例如 ssh 某個機器，題目給的 URL 所有東西都要手動輸入，手打了一題就崩潰受不了。我用訊息聯絡監考官，他要我打電話給 PSI 的客服中心；聯絡的過程中考試會暫停。和客服講完後，他們不會直接幫你立即解決問題，但會幫你開一個 issue ticket 並告訴你單號，之後你可以選擇繼續考試或是直接結束考試。我當下因為太生氣了，直接跟監考官說我要結束考試（中間還一直被阻攔問「你確定嗎？」）。</p><p>事後這樣交卷還拿到了 39 分，後來想想早知道應該繼續做完，把題目看一遍說不定還能低空飛過。</p><p>整個考試我覺得最麻煩的地方，就是在家考試一定要把房間東西都收好，還不能有人講話或開門進來，所以只能挑上班時間請個假來考，還必須在小孩還沒回家前搞定（加上 CKAD 的兩次，我今年已經用了四天假並且搬進搬出房間四次了…）。</p><p>這次考試有出現 Gateway API 的設定，會叫你把 Ingress 架構改成用 Gateway API，也有考到 Helm 的操作，但沒有出現 kustomize 和 upgrade cluster 這類的題目。</p><p>第二次考試有寫完，但是考試中一直很怕 PSI 系統又發瘋，每個指令都慢慢敲很怕又搞爛 PSI。十六題中有兩題是完全不會，其他有寫的、驗證過的都還滿有把握的，但結果出來只是低空飛過，好想知道到底錯在哪啊！</p><h2 id="考試建議"><a href="#考試建議" class="headerlink" title="考試建議"></a>考試建議</h2><ol><li>每個監考官檢查的嚴謹程度不同。我遇過四個監考官有兩個抓很緊，連桌子下的防潮物品都要我撤出房間；有的就看過就放人。為了減少檢查時間，最好把房間都收得沒有任何問題。</li><li>沒事就去開個 Killercoda 或 KodeKloud 的 playground 來練習，像是翻一翻 Static Pod 的 YAML，或熟悉一下每個 kube-system namespace 下的各種 resource。</li><li>Gateway API 在 CKAD 不會練習到，但實戰中有考，建議花點時間練習。</li></ol><p>考完本來想說要休息一陣，但又手賤報了 CKS…</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Bitbucket Compare 與 git diff 的關係：一個 squash merge 引發的疑問</title>
      <link>https://weiblog.me/2025-12-07/git-diff-and-squash-merge/</link>
      <description>今天在公司遇到一個小插曲，同事 J 丟一個問題問說「為什麼我已經 merge 某個 branch 了，但是從 Bitbucket Compare 上還是可以看到差異？」</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/Bitbucket/">Bitbucket</category>
      <category domain="https://weiblog.me/tags/Git/">Git</category>
      <category domain="https://weiblog.me/tags/git-diff/">git-diff</category>
      <category domain="https://weiblog.me/tags/squash-merge/">squash-merge</category>
      <category domain="https://weiblog.me/tags/merge-strategy/">merge-strategy</category>
      <category domain="https://weiblog.me/tags/PR/">PR</category>
      <category domain="https://weiblog.me/tags/workflow/">workflow</category>
      <pubDate>Sun, 07 Dec 2025 13:52:20 GMT</pubDate>
      <content:encoded>
        <![CDATA[<div style="display: flex; justify-content: space-evenly">  <div style="padding: 5px; width: 80%">    <img src="/2025-12-07/git-diff-and-squash-merge/image-0.png" class="">  </div></div><h3 id="為什麼會遇到這個問題？"><a href="#為什麼會遇到這個問題？" class="headerlink" title="為什麼會遇到這個問題？"></a>為什麼會遇到這個問題？</h3><p>同事在他的 repo 裡，從 <code>develop</code> 開了一個 PR，要合併到 <code>test</code> branch。合併完成後，理論上兩個分支應該同步，結果在 Bitbucket Compare 一看，<code>develop</code> 還領先 <code>test</code> 好幾個 commit。這時候我們都愣住了：不是已經 merge 了嗎？怎麼還有差異？</p><h3 id="開始調查"><a href="#開始調查" class="headerlink" title="開始調查"></a>開始調查</h3><p>第一步，我去查 Bitbucket Compare 的 Compare 功能到底怎麼判斷差異。結果發現它底層用的是：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git diff develop...test<br></code></pre></td></tr></table></figure><p>而不是：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git diff develop..test<br></code></pre></td></tr></table></figure><p>這兩個符號差一個點，但語意不同，建議在文章中統一用法說明如下：</p><ul><li>A..B（兩個點）：比較 A 與 B 的 HEAD 差異（等同 <code>git diff A B</code>）。</li><li>A…B（三個點）：比較從共同祖先（merge base）到 B 的差異（等同 <code>git diff $(git merge-base A B) B</code>；也就是從分歧點開始的差異）。</li></ul><p>用下面的圖示來解釋：</p><figure class="highlight subunit"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs subunit">develop:     A --- B --- C<br>                   \<br><span class="hljs-keyword">test:               </span>D --- E<br></code></pre></td></tr></table></figure><ul><li><code>git diff develop..test</code> 比較的是 commit E（test HEAD）和 C（develop HEAD）之間的差異。</li><li><code>git diff develop...test</code> 比較的是從共同祖先（此例為 B）到 E 的差異（等同 <code>git diff B E</code>）。</li></ul><p>因此 Bitbucket Compare 顯示的差異，其實是基於「分歧點（merge base）」，而非單純比較兩個分支的 HEAD。</p><h3 id="關鍵線索"><a href="#關鍵線索" class="headerlink" title="關鍵線索"></a>關鍵線索</h3><p>我注意到這個 PR 沒有在 <code>test</code> branch 產生 merge commit。再去看 Bitbucket 的 merge strategy 設定，發現有人啟用了 squash merge，並設為預設。</p><p>這就解釋了所有問題：squash merge 不會保留原本 <code>develop</code> branch 的 commit 歷史。它會把 PR 的變更壓縮成一個新的 commit，直接新增到 <code>test</code> branch。因此 <code>develop</code> 上的那些原始 commit 並未被帶到 <code>test</code>，分歧點依然存在（在 <code>test</code> 上看到的是新的單一 commit，而不是原來的一連串 commit）。</p><p>用命令驗證：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git diff develop...test<br></code></pre></td></tr></table></figure><p>看到的結果會與 Bitbucket Compare 的結果一致。</p><h3 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h3><p>這不是 Bug，而是 squash merge 的特性。建議使用時機與注意事項：</p><ul><li>若希望 commit 歷史乾淨、把多個小 commit 合併成單一變更，可在個人或短期 feature 分支使用 squash merge。</li><li>若希望保留完整 commit 歷史、或希望 Bitbucket Compare 顯示兩個分支的歷史一致，請使用帶 merge commit 的合併策略（不要 squash）。</li></ul><h3 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h3><ul><li><a href="https://confluence.atlassian.com/bitbucketserver/compare-branches-tags-and-commits-970595881.html">https://confluence.atlassian.com/bitbucketserver/compare-branches-tags-and-commits-970595881.html</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>CKAD 考試心得</title>
      <link>https://weiblog.me/2025-10-03/ckad-preparation/</link>
      <description>雖然知道有證照在求職或面試上幫助不大，但去年還是報考了，一方面是想督促自己唸書；另一方面聽說 CKAD、CKA 等考試方式是上機考</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/Cloud-Native/">Cloud Native</category>
      <category domain="https://weiblog.me/tags/Kubernetes/">Kubernetes</category>
      <category domain="https://weiblog.me/tags/K8s/">K8s</category>
      <category domain="https://weiblog.me/tags/Certification/">Certification</category>
      <category domain="https://weiblog.me/tags/CKAD/">CKAD</category>
      <pubDate>Fri, 03 Oct 2025 15:09:38 GMT</pubDate>
      <content:encoded>
        <![CDATA[<div style="display: flex; justify-content: space-evenly">  <div style="padding: 5px; width: 80%">    <img src="/2025-10-03/ckad-preparation/image-0.png" class="">  </div></div><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>先不要問我考這個可以幹嘛，有證照在求職或面試上真的幫助不大。雖然這麼說但去年還是報考了，一方面是想用＄督促自己唸書；另一方面聽說 CKAD、CKA 等考試方式是上機考，感覺是個不錯的體驗想試試看，<del>畢竟有張證照在公司講話也比較大聲（吧）</del>。</p><p>拖拖拉拉準備半年終於考過了，附上人權</p><div style="display: flex; justify-content: space-evenly">  <div style="padding: 5px; width: 80%">    <img src="/2025-10-03/ckad-preparation/image-1.png" class="">  </div></div><h2 id="準備過程"><a href="#準備過程" class="headerlink" title="準備過程"></a>準備過程</h2><p>這邊列出準備考試有用到的資源：</p><ol><li><a href="https://kubernetes.io/">Kubernetes 官方文件</a> - 常見的 resource 務必查到滾瓜爛熟</li><li><a href="https://www.udemy.com/course/certified-kubernetes-application-developer/">Udemy 課程</a> - 附 KodeKloud Lab 可以大量練習</li><li><a href="https://github.com/dgkanatsios/CKAD-exercises">CKAD exercise</a> - 題目很多，個人認為滿有幫助的</li><li><a href="https://killercoda.com/killer-shell-ckad">Killercoda</a> - Scenario 推薦都做過一遍</li><li>報名考試送的兩次模擬考</li></ol><p>準備上就以 Udemy 課程為主，考前大量做題練手感。這個課程非常推薦，雖然講師是印度人，但沒有什麼口音，每一章節後面都會附上 Lab 來練習，可以說是買 Lab 送課程，用優惠券買相當划算。</p><p>課程上完對常見的 resource 有了解後就開始大量做題。考試範圍其實有包含 Helm、docker &#x2F; podman、Kustomize，但我考兩次只有考過 docker，Helm 和 Kustomize 一題都沒出現。</p><p>做 CKAD exercise (Github repo 那個) 練習時，我是搭配免費的 K8s playground 如 Killercoda、Killershell 或是 KodeKloud 來練習，如果自己電腦有裝 Kind 也是可以用來練習，用這些雲端服務的好處就是用完不用自己去 reset 環境。</p><h2 id="心得"><a href="#心得" class="headerlink" title="心得"></a>心得</h2><p>看其他大神們如 Mike、Tico 的分享，考試好像很簡單（？），實際上並不是這樣，像我這種工作上只有接觸到皮毛的普通人，還是需要花時間練習。</p><p>上完課程其實不難，難的是每天下班累得要死，在送小孩上床後還要有意志力停住想點開遊戲的那根食指，打開課程和印度人共度美好的夜晚。</p><p>第一次考試以差兩分及格飲恨，一部分可以歸咎於考前遇到太多狀況而緊張，另一部分是很多東西都只有一知半解，雖然可以靠 YAML 或是 CLI 快速建立資源，但是要除錯還是得靠對 K8s 的理解。</p><p>有一說法是報名考試附的模擬考比實際考試難，個人體感差不多，只是模擬考題目多了點（約 22 題），實際考試我第一次 20 題、第二次 17 題。考試時間兩小時，如果手速比較慢的話會有時間上的壓力。</p><p>第二次寫完只有一題 ingress 相關的不確定，其他都有寫出來，最後成績是 91 分，有點出乎意料。</p><h2 id="考試建議"><a href="#考試建議" class="headerlink" title="考試建議"></a>考試建議</h2><ol><li>考前會需要排隊進去等待考官檢查你的環境，如果是在家考試，請把房間收得超級乾淨，伸手可及的地方都不要有考試不允許出現的物品。</li><li>準備夠大的螢幕，考試的畫面左方是題目，右邊是一台 Linux VM，那個畫面很小。因為是遠端連線，要有心理準備打字的延遲會跟不上你的手速，操作起來有點痛苦。</li><li>如果不是用筆電的話，攝影機的線一定要夠長，考官會要求拿攝影機拍你的桌子下方。我第一次考試因為這樣拉扯結果造成 webcam 斷線，一旦攝影機斷線你就會被踢出考試系統，從排隊開始的所有流程都要重來一遍。</li><li>實際考試有很多題目是模擬考和 KodeKloud 剛好沒有 cover 到的，因此還是推薦上面有列出的資源都務必練習過。</li><li>前一天一定要睡飽，考試會指定 ssh 到特定機器或是在特定的 namespace 下操作，題目一定要看清楚，不然一串帥氣操作結果 namespace 設成 default 可是一分都沒有。</li><li>準備時務必理解每一個 key 代表的意義。KodeKloud 的 Lab 會很好心的告訴你 key &#x2F; value 填什麼只要無腦複製貼上就好，但考試會像這樣考： <code>請建立一個 Cronjob 叫 grep 使用 XXXX 指令，每 30 分鐘執行一次，失敗不重啟 Pod，保留 196 個成功的 Job 以及 87 個失敗的 Job，每個 Job 執行時間不能超過 8 秒</code>。 看到這個要馬上知道在文件中要搜尋這些關鍵字： <code>successfulJobsHistoryLimit</code>、<code>failedJobsHistoryLimit</code> 、<code>activeDeadlineSeconds</code> 等等。</li><li>能夠用 CLI 一行指令操作的都用 CLI 做，不要去複製 YAML，這樣會太慢。例如建立 Pod 就用 <code>k run tmp --image=busybox</code>。</li><li>不熟 Vim 的人需要練習一下，像是編輯、退出、複製、插入、縮排這些指令熟悉可以省下很多時間。</li><li>在大量練習的過程也要練習怎麼快速驗證操作是正確的，不要想說全部做完再回頭檢查，做完下一些 <code>get</code> <code>describe</code> 檢查不會花太多時間。如果是要測網路，可以用 <code>k run tmp --image=busybox --rm -it --restart=Never -- wget -O- &lt;url&gt;</code> 來快速測試。印象中有一題是要修改一個 deployment，改完發現怎麼 Pod 沒有被更新，查了一下才發現原來是 rollout 被暫停了，如果沒檢查到這題就做白工了。</li></ol><p>其他有想到的之後再補充吧</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>在 Roo Code 上試玩 MCP server</title>
      <link>https://weiblog.me/2025-08-29/play-mcp-with-roo-code/</link>
      <description>本文提供了簡單的 sample code 並介紹如何在 Roo Code 中使用自建 MCP server</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/Roo-Code/">Roo Code</category>
      <category domain="https://weiblog.me/tags/Python/">Python</category>
      <category domain="https://weiblog.me/tags/MCP/">MCP</category>
      <category domain="https://weiblog.me/tags/FastAPI/">FastAPI</category>
      <pubDate>Fri, 29 Aug 2025 07:32:59 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>公司最近吹起一股像是尋找大秘寶的風氣，每個團隊和部門爭相嘗試 MCP 的應用，雖然已經知道近期的團隊 roadmap 會導入 MCP，但目前還沒想到能帶來巨大效益的應用。</p><p>聽說現在要跑起一個 MCP server 並不難，只要有 agent 就能夠開發和測試了，不論是用 Google ADK, Claude 或是 Roo Code，趕緊找一個手邊能用的玩玩看再說。</p><img src="/2025-08-29/play-mcp-with-roo-code/image-0.png" class=""><h2 id="專案設定"><a href="#專案設定" class="headerlink" title="專案設定"></a>專案設定</h2><p>本文將以 VS Code 的 Roo Code 套件作為範例，示範如何連接自建的 MCP 伺服器。這個伺服器本身是獨立的，因此你也可以使用其他支援 MCP 的 agent 來連接，例如 Google ADK 或 Claude。</p><p>首先，建立專案目錄並使用 <code>uv</code> 初始化環境。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs sh">$ <span class="hljs-built_in">mkdir</span> mcp-demo &amp;&amp; <span class="hljs-built_in">cd</span> mcp-demo<br><br>$ uv init<br><br>$ uv add <span class="hljs-string">&quot;mcp[cli]&quot;</span> <span class="hljs-string">&quot;uvicorn[standard]&quot;</span><br></code></pre></td></tr></table></figure><p>接著建立 <code>main.py</code> 來啟動 FastAPI 以及 FastMCP。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># filepath: mcp-demo/main.py</span><br><span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI<br><span class="hljs-keyword">from</span> mcp.server.fastmcp <span class="hljs-keyword">import</span> FastMCP<br><br>mcp = FastMCP()<br>mcp_app = mcp.streamable_http_app()<br><br>app = FastAPI(<br>  lifespan=mcp_app.router.lifespan_context,<br>)<br><br>app.mount(<span class="hljs-string">&quot;/&quot;</span>, app=mcp_app)<br><br><span class="hljs-meta">@app.get(<span class="hljs-params"><span class="hljs-string">&quot;/&quot;</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">read_root</span>() -&gt; <span class="hljs-built_in">dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-built_in">str</span>]:<br>    <span class="hljs-keyword">return</span> &#123;<span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">&quot;FastAPI server is running. MCP is mounted at /mcp&quot;</span>&#125;<br></code></pre></td></tr></table></figure><p>接著來寫一段 code 建立 MCP tool</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># filepath: mcp-demo/main.py</span><br><br><span class="hljs-meta">@mcp.tool()</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">magic_add</span>(<span class="hljs-params">a: <span class="hljs-built_in">int</span>, b: <span class="hljs-built_in">int</span></span>) -&gt; <span class="hljs-built_in">int</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;Add two numbers with a magic formula.&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">return</span> a + <span class="hljs-number">2</span> * b + <span class="hljs-number">3</span> * a * b<br></code></pre></td></tr></table></figure><p>使用以下指令啟動伺服器：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sh">$ uvicorn main:app --port 8000 --reload<br></code></pre></td></tr></table></figure><p>接著在 Roo Code 找到 MCP Servers 的面板，選擇 <code>Edit Project MCP</code> 後會自動打開 <code>.roo/mcp.json</code> 檔案作編輯。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;mcpServers&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;Test Server&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;streamable-http&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;url&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;http://localhost:8000/mcp&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;disabled&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;alwaysAllow&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">]</span><br>    <span class="hljs-punctuation">&#125;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>這邊是告訴 Roo Code (agent) 我們有一個名叫 Test Server 的 MCP server 使用 <code>streamable-http</code> 來和 agent 溝通，並 host 在 localhost:8000。如果是要串接外部的 MCP server 把 type 和 url 稍作修改即可。</p><p>接著到 Roo Code 的 MCP Servers 選單看看有沒有連上 (在 Console 中會看到 API 被呼叫的 log)。</p><img src="/2025-08-29/play-mcp-with-roo-code/image-1.png" class=""><p>如果這邊能夠正確顯示 tool 的內容，就是成功了。</p><p>如果遇到 404 或是 Error log 顯示 <code>RuntimeError: Task group is not initialized. Make sure to use run()</code></p><p>那很可能是 MCP server 沒有正確啟動，或是 URL 路徑設錯。如果是要把 MCP server 掛在 FastAPI 的一個 route 中的話，在 main.py 中的這行很重要</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python">app = FastAPI(<br>  lifespan=mcp_app.router.lifespan_context,<br>)<br></code></pre></td></tr></table></figure><p>接著來用 Roo Code 下個 prompt 測試吧</p><img src="/2025-08-29/play-mcp-with-roo-code/image-2.png" class=""><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><p>這邊展示了如何快速建立一個 MCP server，之後會再補上如何利用 Google ADK 進行開發，以及 agent 是如何和 MCP server 溝通的。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>試用新世代的 Python 套件管理工具 - uv</title>
      <link>https://weiblog.me/2025-08-16/introduction-to-uv/</link>
      <description>本文比較一些常見的 Python 套件管理工具，介紹下一個世代的高效工具： uv</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/Python/">Python</category>
      <category domain="https://weiblog.me/tags/%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86/">套件管理</category>
      <category domain="https://weiblog.me/tags/uv/">uv</category>
      <category domain="https://weiblog.me/tags/Poetry/">Poetry</category>
      <category domain="https://weiblog.me/tags/pip/">pip</category>
      <category domain="https://weiblog.me/tags/pyenv/">pyenv</category>
      <category domain="https://weiblog.me/tags/virtualenv/">virtualenv</category>
      <category domain="https://weiblog.me/tags/Docker/">Docker</category>
      <category domain="https://weiblog.me/tags/PEP-518/">PEP 518</category>
      <category domain="https://weiblog.me/tags/PEP-621/">PEP 621</category>
      <pubDate>Sat, 16 Aug 2025 01:05:32 GMT</pubDate>
      <content:encoded>
        <![CDATA[<img src="/2025-08-16/introduction-to-uv/image-0.png" class=""><div style="margin-bottom: 20px"></div><h2 id="什麼是-uv"><a href="#什麼是-uv" class="headerlink" title="什麼是 uv?"></a>什麼是 uv?</h2><p>在介紹 uv 前，必須提到開發的公司 Astral，也就是開發大名鼎鼎的 Python formatter <strong>ruff</strong> 的公司。他們的野心就是要打造 Python 生態系中最好用的工具。</p><p>uv 就是由 Astral 開發的新一代 Python 套件與環境管理工具。<br>特色是用 Rust 寫的，主打極高速、低記憶體用量，並且把傳統上要用多個工具才能完成的功能（例如 pip、pip-tools、pipx、virtualenv、pyenv）整合到同一個 CLI 中。</p><p>簡單說，uv 讓你可以：</p><ul><li>管理相依套件及 lock file（pyproject.toml &#x2F; uv.lock）</li><li>建立與管理虛擬環境</li><li>安裝與切換 Python 版本</li><li>安裝 CLI 工具（類似 pipx）</li><li>發佈套件到 PyPI</li></ul><h2 id="一起來回顧目前在開發-Python-應用程式時的作法"><a href="#一起來回顧目前在開發-Python-應用程式時的作法" class="headerlink" title="一起來回顧目前在開發 Python 應用程式時的作法"></a>一起來回顧目前在開發 Python 應用程式時的作法</h2><p>如果不使用 Docker 或是 VS Code Dev Containers 開發的話，應該不會和下面這個流程差太多。</p><p>Step 1: 需要先指定 Python 版本，可能用 pyenv、pipenv 或 conda 來鎖定 Python 版本。</p><p>Step 2: 如果前一步的工具沒有附帶虛擬環境，可能就要再使用 Python venv、virtualenvwrapper 或是 poetry 這樣的工具來建立虛擬環境。</p><p>Step 3: 套件管理工具如 pip、poetry、conda 開始幫你解析並安裝相依套件。</p><p>如果使用 poetry 的話，至少要搭配 pyenv 來一起使用，稍嫌麻煩。</p><hr><h2 id="uv-的優勢"><a href="#uv-的優勢" class="headerlink" title="uv 的優勢"></a>uv 的優勢</h2><p>相信有過自行管理 Python 虛擬環境的人應該都能體會這有多麻煩，而 uv 的優勢就不言而喻了。</p><p>uv 讓我們可以：</p><ol><li>用極快的速度安裝專案需要的套件，特別利於在 CI&#x2F;CD 需要 build image，或是需要多環境的測試下減少大量的時間。</li><li>在一個新環境或專案中不用頻繁地安裝切換工具，學習成本變低，提升開發者體驗（DevEx）。</li><li>遵守 PEP 518&#x2F;621，使用 <code>pyproject.toml</code> 來管理相依套件，即使哪天不想用 uv，也能快速切換到支援 <code>pyproject.toml</code> 的工具如 poetry。</li></ol><hr><h2 id="uv-常用的指令"><a href="#uv-常用的指令" class="headerlink" title="uv 常用的指令"></a>uv 常用的指令</h2><h3 id="安裝-uv（macOS-Linux）"><a href="#安裝-uv（macOS-Linux）" class="headerlink" title="安裝 uv（macOS &#x2F; Linux）"></a>安裝 uv（macOS &#x2F; Linux）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">curl -LsSf https://astral.sh/uv/install.sh | sh<br></code></pre></td></tr></table></figure><h3 id="初始化專案"><a href="#初始化專案" class="headerlink" title="初始化專案"></a>初始化專案</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">mkdir</span> demo-uv &amp;&amp; <span class="hljs-built_in">cd</span> demo-uv<br>uv init<br></code></pre></td></tr></table></figure><h3 id="安裝-同步套件"><a href="#安裝-同步套件" class="headerlink" title="安裝&#x2F;同步套件"></a>安裝&#x2F;同步套件</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">uv add fastapi<br>uv add --dev pytest ruff<br></code></pre></td></tr></table></figure><p>如果拿到別人給的 <code>pyproject.toml</code> 有更新，可以使用以下指令同步相依套件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">uv <span class="hljs-built_in">sync</span><br></code></pre></td></tr></table></figure><p><code>uv sync</code> 會根據 <code>pyproject.toml</code> 和 <code>uv.lock</code> 的內容，安裝或更新專案所需的所有相依套件，確保環境與設定檔一致。</p><h3 id="執行程式"><a href="#執行程式" class="headerlink" title="執行程式"></a>執行程式</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">uv run python -c <span class="hljs-string">&quot;import fastapi; print(fastapi.__version__)&quot;</span><br></code></pre></td></tr></table></figure><h3 id="安裝特定-Python-版本"><a href="#安裝特定-Python-版本" class="headerlink" title="安裝特定 Python 版本"></a>安裝特定 Python 版本</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">uv python install 3.12<br></code></pre></td></tr></table></figure><h3 id="設定專案使用版本"><a href="#設定專案使用版本" class="headerlink" title="設定專案使用版本"></a>設定專案使用版本</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">uv python pin 3.12<br></code></pre></td></tr></table></figure><h3 id="輸出成-pip-可以使用的-requirements-txt"><a href="#輸出成-pip-可以使用的-requirements-txt" class="headerlink" title="輸出成 pip 可以使用的 requirements.txt"></a>輸出成 pip 可以使用的 requirements.txt</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">uv <span class="hljs-built_in">export</span> &gt; requirements.txt<br></code></pre></td></tr></table></figure><hr><h2 id="比較-uv-和-pip-的安裝速度"><a href="#比較-uv-和-pip-的安裝速度" class="headerlink" title="比較 uv 和 pip 的安裝速度"></a>比較 uv 和 pip 的安裝速度</h2><p>分別用 pip 和 uv 來安裝 <code>pandas</code>：</p><img src="/2025-08-16/introduction-to-uv/image-1.png" class=""><p>從上圖可以看到，pip 的安裝速度相對較慢，特別是在解析相依套件時。</p><img src="/2025-08-16/introduction-to-uv/image-2.png" class=""><p>而使用 uv 時，安裝速度明顯更快，特別是在處理相依套件的解析與安裝過程中，速度提升了 40 倍。</p><hr><h2 id="測試-uv-在-Docker-中的表現"><a href="#測試-uv-在-Docker-中的表現" class="headerlink" title="測試 uv 在 Docker 中的表現"></a>測試 uv 在 Docker 中的表現</h2><p>底下三個測試都用差不多的 Dockerfile，差別只在安裝 Python 套件的方式。</p><h3 id="Case-1-使用-uv-pip"><a href="#Case-1-使用-uv-pip" class="headerlink" title="Case 1: 使用 uv pip"></a>Case 1: 使用 uv pip</h3><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.10</span>-slim<br><br><span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span><br><br><span class="hljs-keyword">RUN</span><span class="language-bash"> pip install --upgrade pip &amp;&amp; pip install uv</span><br><br><span class="hljs-keyword">COPY</span><span class="language-bash"> pyproject.toml uv.lock /app</span><br><br><span class="hljs-keyword">RUN</span><span class="language-bash"> uv <span class="hljs-built_in">export</span> --no-hashes --no-dev -o requirement.txt</span><br><br><span class="hljs-keyword">RUN</span><span class="language-bash"> uv pip install --system -r requirement.txt</span><br></code></pre></td></tr></table></figure><img src="/2025-08-16/introduction-to-uv/image-3.png" class=""><h3 id="Case-2-使用-uv-sync"><a href="#Case-2-使用-uv-sync" class="headerlink" title="Case 2: 使用 uv sync"></a>Case 2: 使用 uv sync</h3><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.10</span>-slim<br><br><span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span><br><br><span class="hljs-keyword">RUN</span><span class="language-bash"> pip install --upgrade pip &amp;&amp; pip install uv</span><br><br><span class="hljs-keyword">COPY</span><span class="language-bash"> pyproject.toml uv.lock /app</span><br><br><span class="hljs-keyword">RUN</span><span class="language-bash"> uv <span class="hljs-built_in">sync</span> --frozen --no-dev</span><br></code></pre></td></tr></table></figure><img src="/2025-08-16/introduction-to-uv/image-4.png" class=""><h3 id="Case-3-使用-pip"><a href="#Case-3-使用-pip" class="headerlink" title="Case 3: 使用 pip"></a>Case 3: 使用 pip</h3><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.10</span>-slim<br><br><span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span><br><br><span class="hljs-keyword">RUN</span><span class="language-bash"> pip install --upgrade pip &amp;&amp; pip install uv</span><br><br><span class="hljs-keyword">COPY</span><span class="language-bash"> pyproject.toml uv.lock /app</span><br><br><span class="hljs-keyword">RUN</span><span class="language-bash"> uv <span class="hljs-built_in">export</span> --no-hashes --no-dev -o requirement.txt</span><br><br><span class="hljs-keyword">RUN</span><span class="language-bash"> pip install -r requirement.txt</span><br></code></pre></td></tr></table></figure><img src="/2025-08-16/introduction-to-uv/image-5.png" class=""><p>從這個測試可以看到，不論是使用 <code>uv pip</code> 或是 <code>uv sync</code>，花費的時間都是差不多的，而且遠快於直接使用 pip 安裝。</p><hr><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><p>uv 最近是社群炙手可熱的套件，其優異的效能以及開發體驗讓很多開發者直接愛上。建議大家都體驗看看，如果真的有遇到問題，退回去使用 poetry 也不會是很困難的一件事。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>【生時間:高績效時間管理術】讀後心得</title>
      <link>https://weiblog.me/2025-08-08/make-time-reading-notes/</link>
      <description>本文為《生時間》一書的讀後感</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E8%AE%80%E6%9B%B8%E5%BF%83%E5%BE%97/">讀書心得</category>
      <category domain="https://weiblog.me/tags/%E6%99%82%E9%96%93%E7%AE%A1%E7%90%86/">時間管理</category>
      <pubDate>Fri, 08 Aug 2025 10:03:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="生時間-高績效時間管理術-四個步驟，找回自己的時間"><a href="#生時間-高績效時間管理術-四個步驟，找回自己的時間" class="headerlink" title="生時間:高績效時間管理術 - 四個步驟，找回自己的時間"></a>生時間:高績效時間管理術 - 四個步驟，找回自己的時間</h1><p>本書由設計衝刺（Design Sprint）兩位共同發明人 Jake Knapp 和 John Zeratsky 撰寫。他們自稱「時間雙傻」，提出了一個全新的時間管理概念：「生時間（Make Time）」。</p><p>「生時間」是一個簡單的框架，僅用四個步驟就能在一天之中把時間留給自己最重要的「精華」。分別是:</p><ol><li><p><b>精華（Highlight）：</b>選出一天中最想完成的一件事，不一定是工作，也可以是陪家人、閱讀或創作。</p></li><li><p><b>雷射（Laser）： </b>運用策略排除干擾、專注在精華上。書中提供了 40 多種方法。</p></li><li><p><b>提振活力（Energize）： </b>透過運動、飲食與睡眠，提升身心能量。</p></li><li><p><b>反省（Reflect）： </b>每天檢討當天安排與策略，持續優化系統。</p></li></ol><div style="margin: 1.5em 0 1.5em 0;">    <img src="/2025-08-08/make-time-reading-notes/image-1.png" class=""></div><p>我想分別列出四個步驟中自己覺得很有趣耳目一新的策略:</p><h3 id="精華策略"><a href="#精華策略" class="headerlink" title="精華策略"></a>精華策略</h3><ol><li><p>書中提供了作者的行事曆範例，其中將「精華任務」明確地安排在每天的時間區塊上，這是一種對自己下承諾的方式。我注意到 Google 的員工也常在行事曆中加入任務與待辦，這點在一些公開演講與工作分享中也有出現。不禁讓人好奇：這是文化習慣，還是公司內部的默契呢？</p></li><li><p>每個人的精華時間都不同，有些人是「晨型人」有些人是「夜型人」，基因決定我們的精華時間，如果早上真的起不來，可能不是你的錯，另外找尋精華時間吧。</p></li></ol><h3 id="提振活力"><a href="#提振活力" class="headerlink" title="提振活力"></a>提振活力</h3><ol><li>像原始人一樣生活: 假想你有一個從古代穿越來的原始人朋友，模仿他做什麼、吃什麼你就能獲得一樣的能量和健康的身體。人類的身體是被設計來勞動不是久坐的，原始人一天之中要經歷狩獵、逃跑、採集等大量勞力工作，現在人的活動量嚴重不足。原始人吃的都是原型未加工的食物，專挑這種吃就對了，大量的蔬菜、水果、堅果等食物都能幫助身體排毒修復帶來活力。</li><li>正確喝咖啡的時間可以事半功倍: 書中有提到咖啡因提神的原理是咖啡因能阻斷讓人產生疲勞感的腺苷和大腦受器結合，欺騙大腦不會累，中午可以利用小睡前先喝咖啡，然後去睡覺，待咖啡因吸收差不多了，起來時馬上就能覺得精神百倍。</li></ol><h2 id="讀完本書後我採取的行動"><a href="#讀完本書後我採取的行動" class="headerlink" title="讀完本書後我採取的行動"></a>讀完本書後我採取的行動</h2><ol><li>我像原始人一樣行動，能走樓梯不搭電梯，搭大眾運輸能站不坐，每天會可刻意走路和爬樓梯到手錶推薦的目標。</li><li>嘗試在到辦公室時，先花一點時間規劃今天的目標，跳過已經排定的會議，將一個一個任務排進今天的行事曆，這樣像是在對自己以及其他人宣告，這段時間我就是要完成這個任務。</li></ol><h2 id="心得"><a href="#心得" class="headerlink" title="心得"></a>心得</h2><p>因為看到社群太多人推薦這本書，馬上就買來看，和一般時間管理或是教人提高績效的書不同的是，這本書只強調一天當中的那個精華，我們使用各種雷射策略以及提振活力為的就是要確保在一天中能夠執行我們所選出的「精華」。</p><p>很喜歡書中提到的一概念：每日可以確實完成的事項才是成就感的來源，不必在每日的精華時間都拍定一個非常厲害高尚目標，反而是把一個大目標切碎，每日按部就班落實更能帶來快樂。</p><p>因為現在每日都有反思的習慣，有觀察到自己的意志力會隨著體力的消耗而降低，到了晚上把小孩趕上床後意志力已經所剩無幾，這個時候要求自己學習實在是太痛苦，未來可能會嘗試當個「晨型人」，將一天中最有精神的時間留給自己。</p><p>書中特別提醒我們：不需要像網路上的部落客或 Youtuber 一樣，過著「早起、精實又完美」的一天。這些追求完美的生活方式，有時反而會分散注意力、加重焦慮。這段話真讓人感到安慰。</p><blockquote><p>沒有人可以一直當完美的饕客，有完美的生產力、完美的覺察力，又得到完美的休息。</p></blockquote><blockquote><p>我們就是做不到部落客說的，該在清晨五點前去做的 57 件事。其實，就算我們做得到，也不該這麼做。追求完美也會使人注意力不集中——這也是一個閃閃發亮，把你的注意力從真正要務拉走的目標。</p></blockquote><h2 id="參考資料"><a href="#參考資料" class="headerlink" title="參考資料"></a>參考資料</h2><ul><li>生時間 Blog: <a href="https://maketime.blog/">https://maketime.blog/</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>誰吃了我的 zsh?! 在 GCP Workstation 使用 zsh 的方法</title>
      <link>https://weiblog.me/2025-08-02/gcp-workstation-zsh/</link>
      <description>如何在 GCP Cloud Workstation 上使用不會消失的 zsh</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/GCP/">GCP</category>
      <category domain="https://weiblog.me/tags/%E8%87%AA%E5%8B%95%E5%8C%96%E5%B7%A5%E5%85%B7/">自動化工具</category>
      <category domain="https://weiblog.me/tags/zsh/">zsh</category>
      <category domain="https://weiblog.me/tags/Cloud-Workstation/">Cloud Workstation</category>
      <category domain="https://weiblog.me/tags/Shell/">Shell</category>
      <category domain="https://weiblog.me/tags/devtools/">devtools</category>
      <category domain="https://weiblog.me/tags/Linux/">Linux</category>
      <category domain="https://weiblog.me/tags/%E9%96%8B%E7%99%BC%E7%92%B0%E5%A2%83/">開發環境</category>
      <category domain="https://weiblog.me/tags/%E9%9B%B2%E7%AB%AF%E9%96%8B%E7%99%BC/">雲端開發</category>
      <pubDate>Sat, 02 Aug 2025 08:09:52 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近把公司的開發環境從虛擬機器移到 GCP 上 Cloud Workstation（以下都稱為 Workstation），設定完基本的開發環境（如 Python、NVM、Vim 等工具）後就開始開發了。但因為公司的政策，Workstation 會 Idle shutdown，某次重開後發現我的 zsh 居然消失了！</p><span id="more"></span><p>後來在<a href="https://cloud.google.com/workstations/docs/architecture?hl=zh-tw">官方文件</a>看到裡面有提到一段：</p><blockquote><p>永久磁碟：連結至工作站 VM 並掛接至 &#x2F;home 資料夾的永久磁碟，可讓您在工作階段結束後儲存資料和檔案。</p></blockquote><p>才知道在 Cloud Workstation 中，只有 <code>/home</code> 目錄的檔案會被保留，那就表示如果要在 Cloud Workstation 使用 zsh，用 <code>apt install</code> 是行不通的！</p><p>如果想跳過測試過程，直接查看安裝步驟，可以參考<a href="#%E6%87%B6%E4%BA%BA%E5%8C%85">懶人包</a>。</p><h2 id="嘗試安裝的過程"><a href="#嘗試安裝的過程" class="headerlink" title="嘗試安裝的過程"></a>嘗試安裝的過程</h2><ol><li><p>Clone zsh 的原始碼並進入目錄：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ git <span class="hljs-built_in">clone</span> https://github.com/zsh-users/zsh.git &amp;&amp; <span class="hljs-built_in">cd</span> zsh<br></code></pre></td></tr></table></figure></li><li><p>因為 zsh 的 master 是開發測試 branch，要找穩定版需要透過 tag 去找 branch，例如安裝正式版 5.9：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ git checkout tags/zsh-5.9 -b zsh-5.9<br></code></pre></td></tr></table></figure></li><li><p>自己 build 的說明文件寫在 INSTALL 中，文件第一步是設定 configure，所以執行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ ./configure --<span class="hljs-built_in">help</span><br></code></pre></td></tr></table></figure><p>來查看說明。</p></li><li><p>遇到第一個問題：clone 回來的 zsh 目錄沒有 configure 檔案！</p></li><li><p>再次查看文件，發現如果沒有 configure 的話需要先執行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ ./Util/preconfig<br></code></pre></td></tr></table></figure><p>但執行後失敗了，似乎缺少某些套件。</p></li><li><p>透過 ChatGPT 得知需要 Autotools 來輔助 Makefile，並安裝相關套件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ <span class="hljs-built_in">sudo</span> apt install -y autoconf automake libtool<br></code></pre></td></tr></table></figure><p>再次執行 <code>./Util/preconfig</code>，應該就會成功。</p></li><li><p>接著用 configure 來設定輸出的目錄：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ ./configure --prefix=<span class="hljs-variable">$HOME</span>/.local/zsh<br></code></pre></td></tr></table></figure><p>（這裡將 zsh 安裝在 <code>~/.local/zsh</code> 中）。</p></li><li><p>遇到以下錯誤：</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs vim">configure: error: <span class="hljs-comment">&quot;No terminal handling library was found on your system.</span><br>This <span class="hljs-keyword">is</span> probably <span class="hljs-keyword">a</span> library called <span class="hljs-string">&#x27;curses&#x27;</span> <span class="hljs-built_in">or</span> <span class="hljs-string">&#x27;ncurses&#x27;</span>.  You may<br>need <span class="hljs-keyword">to</span> install <span class="hljs-keyword">a</span> package called <span class="hljs-string">&#x27;curses-devel&#x27;</span> <span class="hljs-built_in">or</span> <span class="hljs-string">&#x27;ncurses-devel&#x27;</span> <span class="hljs-keyword">on</span> your<br><span class="hljs-built_in">system</span>.<span class="hljs-comment">&quot;</span><br></code></pre></td></tr></table></figure></li><li><p>缺少套件，安裝 <code>libncursesw5-dev</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ <span class="hljs-built_in">sudo</span> apt install -y libncursesw5-dev<br></code></pre></td></tr></table></figure></li><li><p>再次執行 configure，成功了！</p><img src="/2025-08-02/gcp-workstation-zsh/image-1.png" class=""></li><li><p>最後 build zsh：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ make install<br></code></pre></td></tr></table></figure></li><li><p>因為安裝的目錄在 <code>~/.local/zsh/</code>，所以執行測試時需要用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ ~/.local/zsh/bin/zsh<br></code></pre></td></tr></table></figure><p>來測試。</p></li><li><p>如果一切正常，可以修改 <code>~/.bashrc</code>，讓啟動時自動開啟 zsh。為什麼不能用 <code>chsh</code> 來指定預設 Shell 呢？<br>因為在 Workstation 中，只有 <code>/home</code> 底下的檔案才不會消失，所以即使用了 <code>chsh</code>，下次啟動仍會使用預設的 bash。</p><p>修改 <code>~/.bashrc</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># ~/.bashrc</span><br><span class="hljs-keyword">if</span> [ -f <span class="hljs-string">&quot;<span class="hljs-variable">$HOME</span>/.local/zsh/bin/zsh&quot;</span> ]; <span class="hljs-keyword">then</span><br>  <span class="hljs-built_in">export</span> PATH=<span class="hljs-string">&quot;<span class="hljs-variable">$HOME</span>/.local/zsh/bin:<span class="hljs-variable">$PATH</span>&quot;</span><br>  <span class="hljs-built_in">exec</span> zsh<br><span class="hljs-keyword">fi</span><br></code></pre></td></tr></table></figure></li><li><p>重新啟動 Workstation，輸入 <code>echo $0</code>，如果成功顯示 <code>zsh</code>，那就大功告成了！</p></li></ol><h2 id="懶人包"><a href="#懶人包" class="headerlink" title="懶人包"></a>懶人包</h2><ol><li><p>更新並安裝必要的相依套件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ <span class="hljs-built_in">sudo</span> apt update &amp;&amp; <span class="hljs-built_in">sudo</span> apt install -y \<br>  build-essential libncursesw5-dev libreadline-dev \<br>  libgdbm-dev libssl-dev libffi-dev libtool autoconf git<br></code></pre></td></tr></table></figure></li><li><p>Clone zsh 並切換到穩定版本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ <span class="hljs-built_in">cd</span> ~<br>$ git <span class="hljs-built_in">clone</span> https://github.com/zsh-users/zsh.git<br>$ <span class="hljs-built_in">cd</span> zsh<br>$ git checkout tags/zsh-5.9 -b zsh-5.9<br></code></pre></td></tr></table></figure></li><li><p>設定 configure：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ ./Util/preconfig<br></code></pre></td></tr></table></figure></li><li><p>安裝：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ ./configure --prefix=<span class="hljs-variable">$HOME</span>/.local/zsh<br>$ make -j$(<span class="hljs-built_in">nproc</span>)<br>$ make install<br></code></pre></td></tr></table></figure></li><li><p>修改 <code>~/.bashrc</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ <span class="hljs-built_in">echo</span> <span class="hljs-string">&#x27;&#x27;</span> &gt;&gt; ~/.bashrc<br>$ <span class="hljs-built_in">echo</span> <span class="hljs-string">&#x27;export PATH=&quot;$HOME/.local/zsh/bin:$PATH&quot;&#x27;</span> &gt;&gt; ~/.bashrc<br>$ <span class="hljs-built_in">echo</span> <span class="hljs-string">&#x27;if [ -x &quot;$HOME/.local/zsh/bin/zsh&quot; ]; then exec zsh; fi&#x27;</span> &gt;&gt; ~/.bashrc<br></code></pre></td></tr></table></figure></li></ol><h2 id="後記"><a href="#後記" class="headerlink" title="後記"></a>後記</h2><p>只是想在 Workstation 中用個 oh-my-zsh 居然搞了這麼久，不過這個經驗也滿有趣的，又稍微了解了一點 Workstation，另外要在自己電腦用 VS Code 連到 Workstation 的 VM 又是另一個故事了，改天在寫吧。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Bigtable 和 BigQuery 都很 Big! 他們真的一樣嗎？</title>
      <link>https://weiblog.me/2025-07-30/bigtable-vs-bigquery/</link>
      <description>比較 GCP 兩大資料服務 Bigtable 與 BigQuery 的本質、用途與特性，幫助初學者快速分辨兩者差異與適用場景</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/GCP/">GCP</category>
      <category domain="https://weiblog.me/tags/Bigtable/">Bigtable</category>
      <category domain="https://weiblog.me/tags/BigQuery/">BigQuery</category>
      <category domain="https://weiblog.me/tags/Beginner/">Beginner</category>
      <pubDate>Tue, 29 Jul 2025 21:50:50 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言:"></a>前言:</h2><p>GCP 中的 Bigtable 和 BigQuery 對初學者來說很容易混淆，「都是資料庫而且都有 big 字首，用起來應該差不多吧?」這個就是我拋出來的疑問。在閱讀完<a href="https://cloud.google.com/blog/topics/developers-practitioners/bigtable-vs-bigquery-whats-difference">這篇</a> GCP blog 的文章後做了此摘要。</p><div style="width: 100%; display: flex; justify-content: center;">  <div><img src="/2025-07-30/bigtable-vs-bigquery/image-1.png" class=""></div></div><span id="more"></span><h3 id="從本質上來區分"><a href="#從本質上來區分" class="headerlink" title="從本質上來區分"></a>從本質上來區分</h3><p><b>Bigtable</b> 是一個 wide-column NoSQL database 用在需要低延遲，高吞吐量的使用情境，適合用作 OLTP (Online Transaction Processing)</p><blockquote><p>Bigtable is a NoSQL wide-column database optimized for heavy reads and writes.</p></blockquote><p><b>BigQuery</b> 則是企業級資料倉儲，用來作 OLAP (Online Analytical Processing) 用途</p><blockquote><p>BigQuery is an enterprise data warehouse for large amounts of relational structured data.</p></blockquote><h3 id="特性比較"><a href="#特性比較" class="headerlink" title="特性比較"></a>特性比較</h3><table><thead><tr><th>特性項目</th><th><strong>Bigtable</strong></th><th><strong>BigQuery</strong></th></tr></thead><tbody><tr><td>類型</td><td>Wide-Column NoSQL</td><td>Data Warehouse</td></tr><tr><td>主要用途</td><td>即時寫入讀出、OLTP (無 Transaction)</td><td>離線分析、報表查詢、OLAP</td></tr><tr><td>應用場合</td><td>高吞吐量的應用程式（IoT、AdTech、ML 等）</td><td>大量資料分析、報表統計、BI、SQL 查詢、ML</td></tr><tr><td>查詢方式</td><td>以 Row Key 為主的 API &#x2F; SDK 操作</td><td>標準 SQL 查詢</td></tr><tr><td>延遲表現</td><td>毫秒等級</td><td>秒～十秒以上</td></tr><tr><td>吞吐量擴展</td><td>水平擴展：每加一節點可處理約 10,000 QPS</td><td>虛擬資源，根據查詢負載自動擴展</td></tr><tr><td>資料寫入方式</td><td>高頻寫入，每筆資料可自定欄位，適合大資料量</td><td>批次或串流寫入，用於歷史資料分析</td></tr><tr><td>資料量規模</td><td>TB ～ PB 級</td><td>PB 級</td></tr><tr><td>整合性</td><td>支援 HBase API；整合 Hadoop、Dataflow、Dataproc</td><td>整合 Looker、Data Studio、AI Platform 等</td></tr><tr><td>計費方式</td><td>根據節點與儲存空間收費</td><td>根據掃描資料量計費</td></tr></tbody></table><h3 id="Bigtable-和-BigQuery-共同的特性"><a href="#Bigtable-和-BigQuery-共同的特性" class="headerlink" title="Bigtable 和 BigQuery 共同的特性"></a>Bigtable 和 BigQuery 共同的特性</h3><ol><li>兩個都是雲原生的設計，能夠享用供應商提供的託管服務及雲端架構的優勢</li><li>高達 99.99% SLA</li><li>自動 Sharding 不用在人工處理邏輯</li><li>自動故障修復</li></ol><h3 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h3><p>原文中提到：</p><blockquote><p>It is safe to say that you would serve an application that uses Bigtable as the database but most of the times you wouldn’t have applications performing BigQuery queries.</p></blockquote><p>這句話讓我反思公司內部的使用情境。有些專案團隊確實使用 BigQuery 作為應用程式的資料庫，但也因此遇到 API 回應時間的效能瓶頸。這可能是因為查詢需要大量資料聚合，例如繪製圖表時的操作。雖然 BigQuery 的基本開銷較高，但在某些情境下，省下的時間效益可能超過成本。</p><h4 id="可能在公司使用-Bigtable-的情境"><a href="#可能在公司使用-Bigtable-的情境" class="headerlink" title="可能在公司使用 Bigtable 的情境"></a>可能在公司使用 Bigtable 的情境</h4><p>我們部門主要進行資料視覺化，應用程式需要即時撈取所選區間的資料並繪製成圖表。這類情境通常是讀多寫少，QPS 很低，但對 API 回應時間要求很高，且不需要 Transaction。基於 Bigtable 的低延遲特性，它可能是這類應用程式的理想選擇。</p><h4 id="可能在公司使用-BigQuery-的情境"><a href="#可能在公司使用-BigQuery-的情境" class="headerlink" title="可能在公司使用 BigQuery 的情境"></a>可能在公司使用 BigQuery 的情境</h4><p>我曾設計過一個 Web Analytics 服務，用來追蹤系統使用者行為。這類情境不需要即時讀寫，但需要一次分析大量資料，非常適合使用 BigQuery。它的 SQL 支援和資料聚合能力能有效處理這類需求。</p><p>此筆記用來幫自己快速了解兩個服務的區別，比較可惜的是原文只有大概介紹區別以及特性，沒有講太多實作，未來有機會在深入研究 Bigtable 如 row key 這樣的設計。</p><h3 id="Glossary"><a href="#Glossary" class="headerlink" title="Glossary"></a>Glossary</h3><ul><li>OLTP (Online Transaction Processing): 適合即時讀寫，例如用戶登入、下單。</li><li>OLAP (Online Analytical Processing): 適合查大量資料做統計報表。</li><li>SLA：服務可用性保證，例如 99.99% 表示一年最多可能停機 52 分鐘。<br><strong>附註</strong>：計算方式為 <code>365 天 × 24 小時 × 60 分鐘 × (1 - SLA)</code>，例如 99.99% SLA 的停機時間為 <code>365 × 24 × 60 × 0.01% = 52 分鐘</code>。</li><li>NoSQL：一種不走傳統關聯式資料表設計的資料庫類型，表的設計會依據查詢的方式來設計，不會看到傳統的正規化設計。常見有 key-value（Redis）、document（MongoDB）、wide-column（Bigtable）等。</li><li>雲原生：為雲端架構而設計的系統，強調自動擴展、自動修復、免維運。</li><li>QPS：每秒查詢次數，數字越高代表系統越能撐過高流量的負載。</li><li>Sharding：把資料分散存放到不同機器上，為了擴展與加速。</li></ul><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>GCP Blog: <a href="https://cloud.google.com/blog/topics/developers-practitioners/bigtable-vs-bigquery-whats-difference">Bigtable vs. BigQuery: What’s the difference?</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>2025 郵輪之旅 基隆-長崎-鹿兒島-那霸 (下)</title>
      <link>https://weiblog.me/2025-07-24/2025-cruise-travel-2/</link>
      <description>2025 郵輪遊記下集</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%97%85%E9%81%8A/">旅遊</category>
      <category domain="https://weiblog.me/tags/%E6%97%85%E9%81%8A/">旅遊</category>
      <category domain="https://weiblog.me/tags/%E6%94%9D%E5%BD%B1/">攝影</category>
      <category domain="https://weiblog.me/tags/%E6%97%A5%E6%9C%AC/">日本</category>
      <category domain="https://weiblog.me/tags/2025/">2025</category>
      <pubDate>Thu, 24 Jul 2025 15:14:38 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h2 id="長崎上陸"><a href="#長崎上陸" class="headerlink" title="長崎上陸"></a>長崎上陸</h2><p>這天是這趟航程中在陸上時間最久的，加上想去的景點都在電車可以到達的位置，所以長崎港的行程就全部自由行。</p><p>必去的哥巴拉園離港口很近，不想走路爬坡上去的話可以直接包計程車坐上去，要注意哥巴拉園有兩個入口一個在天主堂旁邊一個在整個花園最上面，記得跟司機講清楚要在那邊下車，才不會出現跟我們一樣叫了兩部車結果在不同入口找不到人的笑話。</p><p>推薦搭車到花園上方的入口，接著一路散步下來是最省時省力的路線。</p><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/glover-garden-2.jpg" class=""></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/glover-garden-3.jpg" class=""></div><div style="display: flex; justify-content: space-evenly">  <div style="padding: 5px; width: 48%">    <img src="/2025-07-24/2025-cruise-travel-2/glover-garden-1.jpg" class="">  </div>  <div style="padding: 5px; width: 48%">    <img src="/2025-07-24/2025-cruise-travel-2/glover-garden-5.jpg" class="">  </div></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/glover-garden-4.jpg" class=""></div><div style="width: 100%; text-align: center;">園區附近的小店家</div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/glover-garden-6.jpg" class=""></div><div style="width: 100%; text-align: center;">遠眺長崎市景</div></br><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 60%">    <img src="/2025-07-24/2025-cruise-travel-2/tram.jpg" class="">  </div></div><div style="width: 100%; text-align: center;">在長崎可以靠著路面電車移動，我們的第二站是諏訪神社。</div></br><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/suwa-jinja-2.jpg" class=""></div><div style="width: 100%; text-align: center;">從正門上去的話就會經過這排雄偉的鳥居，因為有推嬰兒車，我們選擇繞路從後面車道上去。</div></br><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 60%">    <img src="/2025-07-24/2025-cruise-travel-2/suwa-jinja-4.jpg" class="">  </div></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/suwa-jinja-1.jpg" class=""></div><div style="width: 100%; text-align: center;">神社裡面的角落都很好拍</div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/suwa-jinja-3.jpg" class=""></div><p>參訪完諏訪神社後距離上船還有一段時間，加上電車路線也順路，我們在安排了浜町商店街的購物行程。</p><p>在這邊可以買到很多東西，長崎蛋糕如果不想跑去福砂屋本店，這邊也有分店可以買。</p><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/hamamachi-2.jpg" class=""></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/hamamachi-1.jpg" class=""></div><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 60%">    <img src="/2025-07-24/2025-cruise-travel-2/hamamachi-3.jpg" class="">  </div></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/hamamachi-4.jpg" class=""></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/hamamachi-5.jpg" class=""></div><p>晚上在郵輪上盡情的拍攝夜景，這趟很可惜因為時間不夠，不然很想上去稻佐山拍攝夜景。</p><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/nagasaki-nightview-1.jpg" class=""></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/nagasaki-nightview-2.jpg" class=""></div><h2 id="鹿兒島上陸"><a href="#鹿兒島上陸" class="headerlink" title="鹿兒島上陸"></a>鹿兒島上陸</h2><p>鹿兒島想去的點比較多，加上電車不順路，這天我們決定直接包車，事後覺得真的非常划算，不僅節省時間，車上也可以好好休息。</p><p>鹿兒島在過去被稱為薩摩藩，和明治維新有密切關聯：鹿兒島正是西鄉隆盛活躍的舞台，在這邊可以看到很多和西鄉隆盛相關的遺跡。</p><p>聽從領隊的建議，第一站直接衝上城山公園展望台一覽城市美景。</p><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/kagoshima-1.jpg" class=""></div><div style="width: 100%; text-align: center;">城山公園展望台</div><p>接著趕著去鹿兒島水族館看上午十一點的海豚表演。水族館不大，可以來吹冷氣走走，因為鹿兒島實在太熱，還是需要安排這種室內的輕鬆行程。</p><p>中午在著名的天文館商店街用餐，鹿兒島有名的是黑豬和黑牛，因此找間燒肉火鍋是必須的！</p><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 60%">    <img src="/2025-07-24/2025-cruise-travel-2/kagoshima-2.jpg" class="">  </div></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/kagoshima-3.jpg" class=""></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/kagoshima-5.jpg" class=""></div><div style="width: 100%; text-align: center;">天文館商店街</div></br><p>最後一站是著名景點仙巖園，仙巖園原本是島津家的別邸，篤姬在出嫁前曾在這邊生活。島津的後代改名為仙巖園並開放給民眾參觀。</p><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/kagoshima-4.jpg" class=""></div><div style="width: 100%; text-align: center;">鹿兒島著名的觀光景點：仙巖園</div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/kagoshima-6.jpg" class=""></div><div style="width: 100%; text-align: center;">全日本唯一的貓神社</div><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 60%">    <img src="/2025-07-24/2025-cruise-travel-2/kagoshima-7.jpg" class="">  </div></div><div style="width: 100%; text-align: center;">鹿兒島實在有夠熱，在港口旁居然有餐車在賣冰涼的咖啡，實在太邪惡</div></br><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/kagoshima-8.jpg" class=""></div><div style="width: 100%; text-align: center;">  這是我最喜歡的一個角度，從船上可以清楚看到港口邊熱情的日本團體來送行，後方是美麗的櫻島</div></br><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/kagoshima-9.jpg" class=""></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/kagoshima-10.jpg" class=""></div><div style="width: 100%; text-align: center;">最後送上一張從海上拍到的美麗櫻島</div><h2 id="那霸上陸"><a href="#那霸上陸" class="headerlink" title="那霸上陸"></a>那霸上陸</h2><p>郵輪的最後一站是那霸，這天也是在陸上時間最短的，下午才到港。</p><p>因為家人有些人是第一次來沖繩，所以我們分成兩組人：一組帶小朋友去海灘玩沙；另一組去參觀波上宮。</p><p>當天天氣非常炎熱，走在橋上直接曝曬烘烤，感覺皮膚正在一寸一寸的熔化。但是在看到清澈的海水後，暑氣又瞬間被帶走了。</p><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/okinawa-1.jpg" class=""></div><div style="width: 100%; text-align: center;">藍天白雲和清澈海水，這就是沖繩</div></br><div style="display: flex; justify-content: space-evenly">  <div style="padding: 5px; width: 48%">    <img src="/2025-07-24/2025-cruise-travel-2/okinawa-3.jpg" class="">  </div>  <div style="padding: 5px; width: 48%">    <img src="/2025-07-24/2025-cruise-travel-2/okinawa-2.jpg" class="">  </div></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/okinawa-4.jpg" class=""></div><div style="width: 100%; text-align: center;">為了拍美景的代價就是走在橋上被烤熟</div></br><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/okinawa-5.jpg" class=""></div><div style="width: 100%; text-align: center;">第一次一定要來的國際通</div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/okinawa-7.jpg" class=""></div><div style="padding: 5px">  <img src="/2025-07-24/2025-cruise-travel-2/okinawa-8.jpg" class=""></div><div style="width: 100%; text-align: center;">停留時間不多，但還是很喜歡國際通的街景</div></br><p>因為怕小孩餓壞，來不及走到原本查好的餐廳只好在國際通隨便找一家吃，意外的找到一家不錯的餐廳，裡面有各種沖繩料理，海葡萄、塔可飯沖繩麵條都有。</p><p>但店員私心推薦要點拉麵，我一開始還想說這種店的拉麵怎麼會好吃，但送上來一試驚為天人，馬上被分食掉，原本桌上擺滿的食物瞬間黯然失色。</p><p>沖繩只能說是稍作停留，排一到兩個行程就差不多了。</p><p>這趟六天的郵輪和家人一起度過很充實，小朋友也完得很開心，整趟回台灣胖了兩公斤。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>2025 郵輪之旅 基隆-長崎-鹿兒島-那霸 (上)</title>
      <link>https://weiblog.me/2025-07-20/2025-cruise-travel-1/</link>
      <description>2025 郵輪遊記上集</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%97%85%E9%81%8A/">旅遊</category>
      <category domain="https://weiblog.me/tags/%E6%97%85%E9%81%8A/">旅遊</category>
      <category domain="https://weiblog.me/tags/%E6%94%9D%E5%BD%B1/">攝影</category>
      <category domain="https://weiblog.me/tags/%E6%97%A5%E6%9C%AC/">日本</category>
      <category domain="https://weiblog.me/tags/2025/">2025</category>
      <pubDate>Sun, 20 Jul 2025 01:30:25 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>一直想要帶家人搭一趟郵輪，體驗悠哉放空的行程，剛好親友揪了一團家族旅遊我們索性就跟團了。</p><p>上一次搭乘郵輪旅遊已經是 2019 的事了，和上次不同的是，這趟旅程多了幾位新成員，對於 2025 再次搭乘郵輪感到無比興奮。</p><span id="more"></span><h2 id="行前-＆-登船"><a href="#行前-＆-登船" class="headerlink" title="行前 ＆ 登船"></a>行前 ＆ 登船</h2><p>這次搭乘的是麗星郵輪的探索星號，前身聽說是名勝郵輪。</p><p>行程如下：</p><p><b>第一天 (基隆) -&gt; 第二天 (船上) -&gt; 第三天 (長崎) -&gt; 第四天 (鹿兒島) -&gt; 第五天 (沖繩) -&gt; 第六天 (基隆)</b></p><p>郵輪從基隆港出發，因為要帶小孩還有大包小包的行李，我們當天是直接開車到基隆港，停在有點距離的停車場後再轉搭計程車去郵輪碼頭。</p><p>這樣做的好處是可以省去找停車場的時間，而且停車場可以事前預也能知道停放期間的總費用 （但旅行回來後非常後悔開車從台中開到台北，下一趟還是考慮火車或高鐵好了）。</p><p>要特別注意是在東岸登船還是西岸，跑錯可能會影響到托運行李和報到時間，務必事前確認好。</p><p>我們出發是在東岸登船，13:30 報到，要先到一樓托運行李完成後再到二樓辦理報到，托運和報到都需要排隊，所以建議提早到把手續都弄完就可以安心等登船了。</p><p>在報到櫃台領取房卡時，如果沒有特別提出要領取房卡掛繩櫃台是不會主動給的，而且數量有限記得要跟櫃台說。</p><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 90%">    <img src="/2025-07-20/2025-cruise-travel-1/keelungisland.jpg" class="">  </div></div><div style="width: 100%; text-align: center;">基隆嶼</div><hr><p>登船時會驗證你的身分，並收取護照，到整個行程結束結帳後才能領回，在日本行動時海關是以當初複印的 A4 護照影本作為身分的驗證。</p><p>如果沒有印這張或是遺失可是不能入境的，一定要妥善保管。</p><p>登船後就可以自由活動啦，大部分的人都會直接衝餐廳享用美食，行李的部分會在登船後陸續送到倉房的門口，如果有需要立即使用或重要的文件要隨身攜帶。</p><p>等到遊客都上船後，會先進行一次安全演練，所有遊客都需要到指定的集合地點集合，船員簡單示範完救生衣使用方式後就解散了，我想是因為流程很快速時間很短，聽到解散後大家都給予熱烈的掌聲。</p><h2 id="探索星號"><a href="#探索星號" class="headerlink" title="探索星號"></a>探索星號</h2><h3 id="露台客房"><a href="#露台客房" class="headerlink" title="露台客房"></a>露台客房</h3><p>因為不喜歡被關起來的感覺，在房型的選擇上選了露台房，雖然價錢會貴上一些，但有自己的露台可以吹吹海風，再加上能夠先下船我想是值得的。</p><p>探索星號的露台房我覺得兩個人可能是最剛好了，如果三人以上船員會將沙發床攤平併成第二張床，原本小客廳就不見了會讓房間變得很擠。我們家兩大一小住起來還滿剛好，連在一起的大床剛好可以作為小朋友的遊樂園。</p><p>電視能選的節目很少，沒有多媒體中心可以選擇節目，也無法自己接 HDMI 只能當作是個掛在牆上的裝飾品。</p><p>露台的落地窗有安全鎖，可以不用擔心兒童會輕易打開，露台外附了兩張小椅子和一張小桌，可以趁天氣好的時候在這邊看看書，看看海或放空，反正在船上就是時間最多嘛！</p><p>講個這次房間最不滿意的地方，每天晚上躺上床準備進入夢鄉時，總是會聽到蹦蹦蹦蹦的振動音還有音樂，本來想說可能是郵輪剛好有派對活動吧。<br>但是一天兩天到第三天每天都在吵，甚至是半夜還有音樂聲，心想不可能每天都有派對而且到這麼晚吧？上面肯定是酒吧之類的，為了一探究竟，某天晚上偷偷溜下床深怕驚動旁邊熟睡的小魔王，上樓看後，原來對應的是電子娛樂室，此時正好有一男一女開心的在玩跳舞機，這下終於真相大白了。</p><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 90%">    <img src="/2025-07-20/2025-cruise-travel-1/seaview-1.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 90%">    <img src="/2025-07-20/2025-cruise-travel-1/seaview-2.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 90%">    <img src="/2025-07-20/2025-cruise-travel-1/seaview-3.jpg" class="">  </div></div><div style="width: 100%; text-align: center;">船上限定無敵海景</div><h3 id="船上伙食"><a href="#船上伙食" class="headerlink" title="船上伙食"></a>船上伙食</h3><p>講到搭乘郵輪，除了娛樂大家應該最在意食物是不是很難吃了，先說結論，我覺得食物非常好吃！</p><p>探索星號上面總共有三間免費餐廳，以及各式付費餐廳，一個用餐時段可以選一個免費餐廳去吃，付費餐廳則不限。所以去付費餐廳吃不飽還可以去自助餐吃到飽。</p><p>免費餐廳有三家:</p><ol><li>麗都（自助餐）</li><li>星夢（中式合菜）</li><li>百味軒（西式套餐）</li></ol><p>付費餐廳有:</p><ol><li>藍湖 （亞洲料理）</li><li>絲路 （中式合菜）</li><li>星宴 （排餐）</li><li>燒烤餐廳</li><li>海馬（日本料理）</li></ol><p>露台房每天都有 1200 台幣的消費金可以使用，而且可以累計，也就是說第一天沒有使用的話到了第二天可以有 2400、第三天 3600 以此類推。</p><p>如果說免費餐廳是可以吃飽的等級，那付費餐廳可以說真的非常好吃。<br>絲路需要人多菜色才能多樣，而且可以多個房間拆帳付款，適合大家一起去吃，因為很熱門有路過的話可以提前先去預約。</p><p>藍湖是這次旅程吃過最多次的餐廳，裡面菜色非常多樣，特別是鹹酥雞吃起來口味和胖子雞丁很像，晚上想吃宵夜就去外帶一盤，從沒想過郵輪上可以有好吃的宵夜。</p><div style="padding: 5px">  <img src="/2025-07-20/2025-cruise-travel-1/blue-lagoon.jpg" class=""></div><div style="width: 100%; text-align: center;">漂亮的藍湖餐廳</div></br><p>其他幾家考慮到帶小孩麻煩就沒有去嘗試了，但這次六天五夜的旅行，吃的部分給過及格分數。</p><h3 id="船上設施"><a href="#船上設施" class="headerlink" title="船上設施"></a>船上設施</h3><p>因為有大半時間都在海上航行，在船上除了放空外還是需要找點事做，船上有很多設施，大部分都是要付費的。</p><ul><li><p>健身房：擁有無敵海景的健身房，沒有進去使用但在外面看到不少跑步機和飛輪機，如果不想在健身房跑步，甲板上也是有畫一個跑道，只是都離圍欄很近有點危險。</p></li><li><p>游泳池：非常不推的設施，高空滑水道有開放時間，中間的泳池水很深，連成人都踩不到底，不適合小朋友玩水。</p></li><li><p>VR 空間：還算可以，裡面有跳舞機、格鬥街機、賽車等，隔壁可以像網咖一樣付費包 PS4、PS5 來打，便宜很適合殺時間。</p></li><li><p>兒童遊戲室：大推，有帶小朋友的人可以每天帶來放電，裡面有很多玩具還有陪玩姊姊，另外有 Nintendo Switch 可以玩瑪利歐賽車，一天免費上限 2 小時，超過會額外收取費用。</p></li><li><p>七樓大廳，有些時段會開放乘客用超大螢幕玩 Switch，在這邊體驗和陌生人一起用大螢幕玩 Overcooked 也是一種新奇的體驗。</p></li><li><p>電動麻將桌：我覺得是整個郵輪上 CP 值最高的設施了，1000 台幣開一桌，可以打兩小時，還附每個人一杯水果茶，如果沒帶小孩我可能會整天都泡在這裡。</p></li><li><p>賭場：沒什麼興趣只有進去參觀而已。</p></li></ul><p>其他還有 SPA 芳療、按摩這些設施沒有去了解就不多說明了，總之在船上其實不會無聊，如果是三五好友一起出遊，有帶上桌遊的話反而會覺得時間怎麼這麼不夠。</p><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 90%">    <img src="/2025-07-20/2025-cruise-travel-1/starnavigator-1.jpg" class="">  </div></div><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 90%">    <img src="/2025-07-20/2025-cruise-travel-1/starnavigator-2.jpg" class="">  </div></div><h3 id="小知識"><a href="#小知識" class="headerlink" title="小知識"></a>小知識</h3><ol><li>房間走道的地毯是很多條鯉魚，魚頭的方向就是船頭，魚尾的方向就是船尾，只要上船後對各個設施的相對位置有概念，就不會迷路了。</li><li>船上可以透過 QR code 開啟郵報網頁，裡面會有每天的節目以及到港、離港時間。</li><li>本來想說在船上夠高，應該可以拍夜景吧就帶了腳架，但船移動時非常晃，腳架一點用都沒有，不如手持加上機身防抖。</li></ol><h3 id="和上次搭的鑽石公主號的差異"><a href="#和上次搭的鑽石公主號的差異" class="headerlink" title="和上次搭的鑽石公主號的差異"></a>和上次搭的鑽石公主號的差異</h3><ol><li>公主號的雙人露台房客廳的空間比較大，但床是上下鋪比較小，電視有兩台。</li><li>電視每天都會有新的電影可以選來看，雖然沒有中文字幕但我還是可以坐在沙發上看一整天。</li></ol><h2 id="後記"><a href="#後記" class="headerlink" title="後記"></a>後記</h2><p>回程是三點左右到達基隆港，接著要開約四小時的車程回台中，想到就覺得累死。</p><p>大約開到汐止就開始暴雨，車流很多，視線很差所以車速也不快。屋漏偏逢連夜雨，開到一半發現胎壓在下降，幸好離新店交流道不遠，胎壓也沒有掉的很快剛好撐到交流道附近的輪胎行，有驚無險。</p><p>下一趟搭郵輪，我想我還是乖乖搭火車好了。</p><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 60%">    <img src="/2025-07-20/2025-cruise-travel-1/keelungharbor-1.jpg" class="">  </div></div><div style="width: 100%; text-align: center;">回程拍到美麗的基隆港</div>]]>
      </content:encoded>
    </item>
    <item>
      <title>拆解 Streamlit 的神秘資料流</title>
      <link>https://weiblog.me/2025-07-17/streamlit-dataflow-experiment/</link>
      <description>用 Streamlit 實測 WebSocket 與 Protobuf 如何協作，並分享如何觀察與解碼傳輸內容的實戰經驗</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/">技術筆記</category>
      <category domain="https://weiblog.me/tags/devtools/">devtools</category>
      <category domain="https://weiblog.me/tags/streamlit/">streamlit</category>
      <category domain="https://weiblog.me/tags/protobuf/">protobuf</category>
      <pubDate>Thu, 17 Jul 2025 14:21:03 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近公司在推行用 Streamlit 來建構專案的 prototype，讓資料科學家可以快速建立可互動的 UI。我自己試玩了一下覺得很不錯，只要寫點 Python code 就能快速生出漂亮的 UI 互動元件，自己刻可能都要花上一點時間。</p><span id="more"></span><p>在做 side project 的過程中，開始對它的架構產生興趣：在前端操作完 UI 元件後，後端是怎麼接收這個事件並做出回應的呢？打開 Debugger Tool 翻遍了所有 request 都沒看到疑似交換資料的 API call。</p><img src="/2025-07-17/streamlit-dataflow-experiment/image-1.png" class=""><p>也沒看到 HTML 有被重新請求，那合理懷疑是走 WebSocket 了。後來問了 ChatGPT 才確認是 WebSocket 搭配 Protobuf 的實作。</p><p>眼見為憑，自己還是要動手驗證看看。</p><h2 id="拿自己的-Streamlit-Side-Project-來實驗"><a href="#拿自己的-Streamlit-Side-Project-來實驗" class="headerlink" title="拿自己的 Streamlit Side Project 來實驗"></a>拿自己的 Streamlit Side Project 來實驗</h2><p>快速寫一個 Streamlit 元件：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># app.py</span><br><span class="hljs-keyword">import</span> streamlit <span class="hljs-keyword">as</span> st<br><br>st.title(<span class="hljs-string">&quot;詐騙文案分析器&quot;</span>)<br><br>text = st.text_area(<span class="hljs-string">&quot;請貼上詐騙文案：&quot;</span>)<br><br><span class="hljs-keyword">if</span> st.button(<span class="hljs-string">&quot;分析詐騙文案&quot;</span>):<br>    <span class="hljs-keyword">if</span> text:<br>        st.write(text)<br>        st.write(<span class="hljs-string">&quot;分析中...&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;後端待分析的文案：<span class="hljs-subst">&#123;text&#125;</span>&quot;</span>)<br>    <span class="hljs-keyword">else</span>:<br>        st.warning(<span class="hljs-string">&quot;請先貼上詐騙文案！&quot;</span>)<br></code></pre></td></tr></table></figure><p>用 Streamlit 啟動：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs routeros">$ streamlit <span class="hljs-built_in">run</span> app.py<br></code></pre></td></tr></table></figure><p>可以立即得到一個精美的 Streamlit 網頁，同時打開 Dev Tool 觀測 WebSocket 的建立。</p><p>這邊可以看到有一個 HTTP 101 Upgrade 使用 WebSocket 的請求，代表瀏覽器已經和後端的伺服器建立了連線。</p><img src="/2025-07-17/streamlit-dataflow-experiment/image-3.png" class=""><p>接著我們試著輸入一段文字送給後端，並觀察 WebSocket 傳送的訊息，可以得到一個 base64 編碼的二進位檔案。</p><p>補充：後來才知道現在瀏覽器已經可以直接查看 WebSocket 的訊息了，以前都不知道。手上版本的 Firefox 沒辦法這樣查看，後來就改成用 Chrome 來實驗。</p><h2 id="Decode-Protobuf-訊息"><a href="#Decode-Protobuf-訊息" class="headerlink" title="Decode Protobuf 訊息"></a>Decode Protobuf 訊息</h2><p>去找了目前版本的 Streamlit 原始碼中定義的 schema，自己轉檔成 Python。因為對 Protobuf 不夠熟，試了很久都失敗。</p><p>最後直接把 base64 字串貼到線上工具 <a href="https://www.protobufpal.com/">protobufpal</a> 解出如下圖的 JSON 格式。</p><img src="/2025-07-17/streamlit-dataflow-experiment/image-4.png" class=""><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;subMesssage_11&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;subMesssage_1&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;subMesssage_2&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;subMesssage_1&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>        <span class="hljs-attr">&quot;string_1&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;$$ID-b2e0ce08522d671c761a53ba564ac075-None&quot;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-attr">&quot;bytes_6&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>          <span class="hljs-attr">&quot;0&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">72</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;1&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">105</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;2&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">32</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;3&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">230</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;4&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">136</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;5&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">145</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;6&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">230</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;7&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">152</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;8&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">175</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;9&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">32</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;10&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">74</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;11&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">111</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;12&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">101</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;13&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">32</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;14&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">232</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;15&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">128</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;16&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">129</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;17&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">229</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;18&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">184</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;19&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">171</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;20&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">229</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;21&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">149</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;22&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">166</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;23&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">239</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;24&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">188</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;25&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">140</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;26&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">230</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;27&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">156</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;28&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">128</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;29&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">232</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;30&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">191</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;31&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">145</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;32&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">233</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;33&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">129</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;34&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">142</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;35&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">231</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;36&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">154</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;37&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">132</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;38&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">229</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;39&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">165</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;40&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">189</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;41&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">229</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;42&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">151</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;43&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">142</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;44&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">239</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;45&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">188</span><span class="hljs-punctuation">,</span><br>          <span class="hljs-attr">&quot;46&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">159</span><br>        <span class="hljs-punctuation">&#125;</span><br>      <span class="hljs-punctuation">&#125;</span><br>    <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;string_3&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;3f41e546893dc64b71aaacad12cad815&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;subMesssage_4&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;subMesssage_5&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;subMesssage_8&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;string_1&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Asia/Taipei&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;int_2&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">18446744073709551136</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;string_3&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;zh-TW&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;string_4&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;http://localhost:8501/&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;int_5&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">0</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;string_6&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;dark&quot;</span><br>    <span class="hljs-punctuation">&#125;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>拿到這串二進位資料後，用 Python 轉回 UTF-8 字串：</p><img src="/2025-07-17/streamlit-dataflow-experiment/image-5.png" class=""><p>就得到當初我輸入進 Streamlit UI 的文字了！</p><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><p>本來是想快速建立一個 Streamlit 專案來體驗看看，順便體驗一下 Cursor 和 Uvicorn 等沒用過的開發工具，順手做了實驗驗證一下 ChatGPT 給的答案。</p><ul><li>Protobuf 的運作機制還不夠熟，也許以後可以自己實作玩一次。</li><li>了解到 WebSocket 搭配 Protobuf 具有幾個優點：<ol><li>瀏覽器和後端可以雙向溝通，透過 WebSocket 可以即時把資料送到後端。</li><li>可以從 Dev Tool 看到訊息的大小都是以 B 計算，非常輕量。</li></ol></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Hello from Kevin</title>
      <link>https://weiblog.me/2025-07-16/hello-from-Kevin/</link>
      <description>Kevin blog 的第一篇文章！</description>
      <author>wei</author>
      <pubDate>Wed, 16 Jul 2025 14:00:49 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="Kevin-的第一篇文章"><a href="#Kevin-的第一篇文章" class="headerlink" title="Kevin 的第一篇文章"></a>Kevin 的第一篇文章</h1><p>一直想要有個自己的部落格來寫點<del>東西</del>廢文, 花了一點時間把 Hexo 和 Fluid theme 弄起來，很有成就感呀！</p><p>之後在陸續把其他平台上的文章慢慢搬過來</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>【閱讀力：在碎片化時代建立高效輸入與輸出系統】讀後心得</title>
      <link>https://weiblog.me/2023-03-01/reading-efficiency-system/</link>
      <description>本文為佐佐木俊尚《閱讀力》一書的讀後感與實踐筆記，探討在知識碎片化時代如何提升資訊吸收與轉化效率</description>
      <author>wei</author>
      <category domain="https://weiblog.me/categories/%E8%AE%80%E6%9B%B8%E5%BF%83%E5%BE%97/">讀書心得</category>
      <category domain="https://weiblog.me/tags/%E9%96%B1%E8%AE%80%E5%8A%9B/">閱讀力</category>
      <category domain="https://weiblog.me/tags/%E6%95%A3%E6%BC%AB%E5%8A%9B/">散漫力</category>
      <category domain="https://weiblog.me/tags/%E8%BC%B8%E5%85%A5%E8%88%87%E8%BC%B8%E5%87%BA/">輸入與輸出</category>
      <category domain="https://weiblog.me/tags/%E7%9F%A5%E8%AD%98%E7%AE%A1%E7%90%86/">知識管理</category>
      <category domain="https://weiblog.me/tags/%E5%AA%92%E9%AB%94%E8%AD%98%E8%AE%80/">媒體識讀</category>
      <category domain="https://weiblog.me/tags/RSS/">RSS</category>
      <pubDate>Wed, 01 Mar 2023 02:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<blockquote><p>本文最初發表於 2023 年，後搬遷至此歸檔。</p></blockquote><hr><p>標題乍看之下像是在讀哪本輕小說，一般人看到這種「工具書」可能連翻都不會翻。但這並不是一本工具書，更像是作者分享自己吸收資訊的「目的」與「心法」。</p><p>佐佐木俊尚是一位作家與新聞工作者。吸引我閱讀這本書的，除了書名外，也包括對媒體從業者如何面對海量資訊的好奇。本書介紹了各類新聞媒體與書籍的閱讀方法，並提出了「散漫力」的概念，用來提升生活與工作的效率。</p><blockquote><p>閱讀的主要目標，是為了得到多元觀點與知識，並將這些轉化為「知肉」。</p></blockquote><p>「知肉」是作者自創的詞，用來形容那些內化為自身、變成可用觀點與行動準則的知識。本書整體就是圍繞著如何生成知肉而展開。</p><img src="/2023-03-01/reading-efficiency-system/image-2.jpg" class=""><h2 id="媒體閱讀的心法：光譜與象限"><a href="#媒體閱讀的心法：光譜與象限" class="headerlink" title="媒體閱讀的心法：光譜與象限"></a>媒體閱讀的心法：光譜與象限</h2><p>作者提出媒體的兩種分類方式：</p><ol><li><p><strong>深度</strong>：</p><ul><li>平行媒體：資訊量多但內容淺</li><li>垂直媒體：主題聚焦、分析深入</li></ul></li><li><p><strong>立場</strong>：</p><ul><li>偏頗媒體：立場明確，觀點主觀</li><li>中立媒體：立場相對客觀，報導為主</li></ul></li></ol><p>將這兩種光譜畫成二維象限，會得到：</p><ul><li>平行媒體 — 中立</li><li>平行媒體 — 偏頗</li><li>垂直媒體 — 中立</li><li>垂直媒體 — 偏頗</li></ul><blockquote><p>方法：先從平行中立媒體取得輪廓 → 再從垂直中立媒體取得觀點 → 結合為新聞的全貌。</p></blockquote><h2 id="推式資訊-vs-拉式資訊"><a href="#推式資訊-vs-拉式資訊" class="headerlink" title="推式資訊 vs 拉式資訊"></a>推式資訊 vs 拉式資訊</h2><p>作者提醒，我們每天都在接收來自社群平台的「推式資訊」，這些資訊容易形成過濾泡泡，導致營養不良的「知肉」。</p><p>反之，應該學會主動出擊，建立自己的「拉式資訊」系統。作者的方法如下：</p><ol><li>使用 RSS 閱讀器訂閱優質來源</li><li>初步篩選後，將有興趣的文章儲存至 Instapaper&#x2F;Pocket</li><li>深度閱讀後，將內容與筆記整合進 Notion 等筆記系統</li></ol><p>這提醒我：我以往閱讀習慣是隨機看到就讀，閱讀過程中常中斷而導致遺忘。其實應該將「資訊收集」與「資訊吸收」拆分開來，在不同時間完成。</p><h2 id="散漫力與多工的活用"><a href="#散漫力與多工的活用" class="headerlink" title="散漫力與多工的活用"></a>散漫力與多工的活用</h2><p>作者主張：「不要強迫專注，而是學會活用散漫」。</p><p>與其勉強自己專注，不如認清我們很難長時間集中，將注意力有限的資源花在真正需要投入的任務上，其餘瑣事則安排在「散漫狀態」下完成。</p><p>這與電腦的 multitasking 不同。人腦不能像電腦隨時中斷、切換、重啟。我們應該依照任務的專注力需求分配如下：</p><blockquote><p>例如：輕 — 輕 — 重 — 輕 — 重</p></blockquote><p>這樣交錯安排能達成最好的輸出。</p><div style="width: 100%; display: flex; justify-content: center">  <div style="padding: 5px; width: 50%">    <img src="/2023-03-01/reading-efficiency-system/image-1.jpg" class="">  </div></div><h3 id="我的電子書閱讀筆記方式："><a href="#我的電子書閱讀筆記方式：" class="headerlink" title="我的電子書閱讀筆記方式："></a>我的電子書閱讀筆記方式：</h3><ul><li>使用 Kindle 做重點標註</li><li>使用手機&#x2F;電腦即時筆記到 Notion</li><li>將 clippings 檔上傳到 Clippings 或匯入 Notion 整理</li><li>最後將筆記轉化為自己的觀點文章（例如這篇）</li></ul><h3 id="我的後續行動："><a href="#我的後續行動：" class="headerlink" title="我的後續行動："></a>我的後續行動：</h3><ul><li>平衡「輸入」與「輸出」，以寫作檢驗是否真正獲得知肉</li><li>開始使用 RSS Reader 系統化閱讀流程</li><li>把家中說明書、資料電子化掃描進 NAS，減少紙本雜亂</li><li>更有效率地整合手邊工具（Instapaper、Notion）</li></ul><hr><p>這本書帶給我最大的啟發是：</p><blockquote><p>不在於專注力提升技巧，而是用「系統設計」去面對自己的分心本能，將之化為可用的能力。</p></blockquote>]]>
      </content:encoded>
    </item>
  </channel>
</rss>
