<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Liwen's Digital Garden Blog</title>
        <link>https://garden.liwen.studio/blog</link>
        <description>Liwen's Digital Garden Blog</description>
        <lastBuildDate>Tue, 14 Jul 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-Hant</language>
        <item>
            <title><![CDATA[🎯 從被問倒到打怪升級：圖解高頻寫入與高可用架構演進史]]></title>
            <link>https://garden.liwen.studio/blog/high-concurrency-architecture</link>
            <guid>https://garden.liwen.studio/blog/high-concurrency-architecture</guid>
            <pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[面對 1000 台設備同時回傳狀態時，如何避免 API 與資料庫被高頻寫入壓垮？本文從單體寫入演進到 Nginx、Kafka、Redis、Kubernetes 與 CQRS 的高可用監控架構。]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言一場面試帶來的架構震撼教育">前言：一場面試帶來的架構震撼教育<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#%E5%89%8D%E8%A8%80%E4%B8%80%E5%A0%B4%E9%9D%A2%E8%A9%A6%E5%B8%B6%E4%BE%86%E7%9A%84%E6%9E%B6%E6%A7%8B%E9%9C%87%E6%92%BC%E6%95%99%E8%82%B2" class="hash-link" aria-label="前言：一場面試帶來的架構震撼教育的直接連結" title="前言：一場面試帶來的架構震撼教育的直接連結" translate="no">​</a></h2>
<p>今天去面試時，面試官拋出了一個問題：「如果是設備監控系統，當同時有 1000 台以上的設備回傳狀態時，你會怎麼寫入資料庫？」</p>
<p>當時的我根本沒遇過高頻跟高併發的情境，當下只是滿頭問號看著他，心想：「阿不就把資料寫進資料庫嗎？寫個 <code>INSERT INTO</code> 語法不就好了，這有什麼好問的？」</p>
<p>想當然爾，面試結束後我還是一臉懵，直到回來的路上跟學姊討論才發現原來資料量大到一個程度時會產生「高頻海量數據寫入」的致命效能瓶頸，這瞬間激起了我的好奇心於是在回程的車上我直接開了 Gemini 一路追問到底，這才終於推開了後端架構的另一扇大門也深刻體會到自己原本的想法有多麼井底之蛙，這篇筆記就是這趟探索之旅的總結。</p>
<!-- -->
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-設備監控系統海量數據的架構演進史">🎯 設備監控系統：海量數據的架構演進史<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#-%E8%A8%AD%E5%82%99%E7%9B%A3%E6%8E%A7%E7%B3%BB%E7%B5%B1%E6%B5%B7%E9%87%8F%E6%95%B8%E6%93%9A%E7%9A%84%E6%9E%B6%E6%A7%8B%E6%BC%94%E9%80%B2%E5%8F%B2" class="hash-link" aria-label="🎯 設備監控系統：海量數據的架構演進史的直接連結" title="🎯 設備監控系統：海量數據的架構演進史的直接連結" translate="no">​</a></h2>
<p>當系統面臨上千台設備每秒連續回傳狀態時，傳統那種「API 收到資料就直接 Insert 進資料庫」的寫法會因為瞬間湧入的大量併發連線先壓垮 API Server，且後續瘋狂的高頻寫入也會讓資料庫的磁碟 I/O 直接癱瘓，所以我們必須在架構上進行分層擊破。</p>
<p>在面對<strong>高併發連線</strong>時，我們會在最前方加上 Nginx 當作反向代理把海量連線平均分發給後端多台 API Server，接著為了解決<strong>高頻寫入</strong>，API Server 收到資料後不再直接操作資料庫而是極速丟進 Kafka 訊息隊列，再由後方的 Consumer 慢慢把資料拉出來做 Batch Insert 藉由這種「削峰填谷」來保護資料庫。</p>
<p>除此之外為了應付管理員在 Dashboard 上的<strong>高頻讀取</strong>需求，我們引入 Redis 當作記憶體快取，Consumer 在寫入資料庫存檔的同時順手把最新狀態覆蓋到 Redis 裡，往後所有的即時查詢都只會命中極速的 Redis 從而徹底解放資料庫的讀取壓力並落實資料分級。</p>
<p>到了架構成熟期為了解決伺服器的單點故障並實現<strong>高可用性 (HA)</strong>，我們會幫 Nginx 綁定 Keepalived 虛擬 IP 並幫 Redis 配置 Sentinel 哨兵模式以確保任何一台當機時都能在幾秒內無縫切換到備援機，最後若將系統搬上 Kubernetes 我們更會導入 CQRS 讀寫分離的微服務概念把 API 徹底拆分成負責接流量的 Write API 與負責供查詢的 Read API，讓兩條動線完全解耦且能獨立自動擴縮容，這才算是一個真正具備彈性與高容錯的現代化監控架構。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-核心名詞定義先搞懂這三個術語">1. 核心名詞定義（先搞懂這三個術語）<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#1-%E6%A0%B8%E5%BF%83%E5%90%8D%E8%A9%9E%E5%AE%9A%E7%BE%A9%E5%85%88%E6%90%9E%E6%87%82%E9%80%99%E4%B8%89%E5%80%8B%E8%A1%93%E8%AA%9E" class="hash-link" aria-label="1. 核心名詞定義（先搞懂這三個術語）的直接連結" title="1. 核心名詞定義（先搞懂這三個術語）的直接連結" translate="no">​</a></h2>
<p>為了解決系統崩潰的問題，我們得先搞懂這三個常被混淆、卻又息息相關的名詞：</p>
<ul>
<li class="">
<p><strong>高併發 (High Concurrency)</strong>：</p>
<ul>
<li class=""><strong>白話說</strong>：系統在「同一個瞬間」能同時接待多少個請求，考驗的是系統的寬度。</li>
<li class=""><strong>痛點</strong>：1000 台設備「同時」打 API 對 Server 來說就是瞬間爆出 1000 個連線，如果伺服器的執行緒或記憶體不夠，來不及處理的連線就會被塞在門口排隊甚至直接被踢掉（Timeout 或 Connection Refused）。</li>
</ul>
</li>
<li class="">
<p><strong>高頻 (High Frequency)</strong>：</p>
<ul>
<li class=""><strong>白話說</strong>：特定動作發生的「頻率極高」且「連續不斷」，考驗的是系統的持久耐力與吞吐量（每秒能讀寫幾次）。</li>
<li class=""><strong>痛點</strong>：這 1000 台設備不是傳一次就沒事了而是「每秒」都在狂傳，這會對資料庫產生極高頻率的連續寫入壓力，像 MySQL 這種傳統資料庫面對這種狀況很容易因為硬碟 I/O 跟不上或是資料表被 Lock 死而直接把整個系統拖垮。</li>
</ul>
</li>
<li class="">
<p><strong>高可用性 (High Availability, HA)</strong>：</p>
<ul>
<li class=""><strong>白話說</strong>：系統的「容錯與備援能力」，就算硬體壞了或軟體當機系統也能在極短時間內自動恢復，達到「服務幾乎不中斷」的境界。</li>
<li class=""><strong>痛點</strong>：假設所有設備都只連同一台 API Server 只要這台機器一壞（這叫單點故障 SPOF），1000 台設備就會瞬間全部斷線且資料也會跟著遺失，高可用性就是為了防範這種毀滅性災難。</li>
</ul>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-如果用最原始的一條龍架構會怎樣">2. 如果用最原始的一條龍架構會怎樣？<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#2-%E5%A6%82%E6%9E%9C%E7%94%A8%E6%9C%80%E5%8E%9F%E5%A7%8B%E7%9A%84%E4%B8%80%E6%A2%9D%E9%BE%8D%E6%9E%B6%E6%A7%8B%E6%9C%83%E6%80%8E%E6%A8%A3" class="hash-link" aria-label="2. 如果用最原始的一條龍架構會怎樣？的直接連結" title="2. 如果用最原始的一條龍架構會怎樣？的直接連結" translate="no">​</a></h2>
<p><strong>情境設定</strong>：
我們以「設備監控系統」為例，假設有 1000 台設備每秒回傳一次狀態而且每次回傳裡面有 1000 個數據點。</p>
<p><strong>原始資料流程</strong>：
一開始最直覺的寫法通常是一條龍的，包含「寫入」與「讀取」兩條動線：</p>
<ol>
<li class=""><strong>寫入動線</strong>：<code>感測器回傳狀態</code> ➡️ <code>API Server 接收</code> ➡️ <code>寫入資料庫</code></li>
<li class=""><strong>讀取動線</strong>：<code>管理員打開 Dashboard 網頁</code> ➡️ <code>API Server 接收請求</code> ➡️ <code>去資料庫撈最新狀態</code> ➡️ <code>回傳畫面</code></li>
</ol>
<!-- -->
<hr>
<blockquote>
<p>⚠️ <strong>會發生什麼慘劇？</strong>
這個架構在設備跟使用者都不多的時候沒什麼感覺，但如果在「1000 台設備每秒同時回傳」的規模下瞬間的高併發絕對會壓垮那台可憐的 API Server，且它瘋狂 Insert 的動作也會引發高頻寫入問題直接塞爆資料庫的硬碟 I/O，這時候如果還疊加了「多位管理員同時打開 Dashboard 瘋狂 F5 刷新畫面」的高頻讀取需求，本來就在苦撐的資料庫就會徹底死當。</p>
</blockquote>
<p>所以我們必須把架構拆開來，一關一關解決：</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-實際架構演進與解法">3. 實際架構演進與解法<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#3-%E5%AF%A6%E9%9A%9B%E6%9E%B6%E6%A7%8B%E6%BC%94%E9%80%B2%E8%88%87%E8%A7%A3%E6%B3%95" class="hash-link" aria-label="3. 實際架構演進與解法的直接連結" title="3. 實際架構演進與解法的直接連結" translate="no">​</a></h2>
<p>當這 1000 台設備透過 API Server 將數據寫入系統時，每一層都會面臨不同的壓力我們得這樣解：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="第一關解決-api-server-的高併發被塞爆">第一關：解決 API Server 的「高併發」被塞爆<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#%E7%AC%AC%E4%B8%80%E9%97%9C%E8%A7%A3%E6%B1%BA-api-server-%E7%9A%84%E9%AB%98%E4%BD%B5%E7%99%BC%E8%A2%AB%E5%A1%9E%E7%88%86" class="hash-link" aria-label="第一關：解決 API Server 的「高併發」被塞爆的直接連結" title="第一關：解決 API Server 的「高併發」被塞爆的直接連結" translate="no">​</a></h3>
<ul>
<li class=""><strong>解法：加上 Nginx 做負載平衡 (Load Balancing)</strong>
<ul>
<li class="">我們在最前面用 Nginx 當作反向代理入口，把大量併發請求「平均分發」給後端多台 API Server 處理這樣就不怕單一伺服器被壓垮了。</li>
</ul>
</li>
<li class=""><strong>👉 白話舉例</strong>：想像設備是 1000 個同時衝進餐廳的客人而單台 API 就是唯一的服務生，他絕對會崩潰，所以 Nginx 就像門口的帶位主管他不端菜只負責看哪桌服務生比較閒就把客人帶過去，大家分攤著接待就不會有人累垮。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="第二關解決資料庫的高頻寫入卡死">第二關：解決資料庫的「高頻寫入」卡死<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#%E7%AC%AC%E4%BA%8C%E9%97%9C%E8%A7%A3%E6%B1%BA%E8%B3%87%E6%96%99%E5%BA%AB%E7%9A%84%E9%AB%98%E9%A0%BB%E5%AF%AB%E5%85%A5%E5%8D%A1%E6%AD%BB" class="hash-link" aria-label="第二關：解決資料庫的「高頻寫入」卡死的直接連結" title="第二關：解決資料庫的「高頻寫入」卡死的直接連結" translate="no">​</a></h3>
<ul>
<li class=""><strong>解法：用 Kafka 訊息隊列 (Message Queue) 來削峰填谷</strong>
<ul>
<li class="">API Server 收到數據後不要直接存資料庫而是快速丟進 Kafka（它寫入效能超強不怕大流量），接著由後端的 Consumer 程式依照資料庫吃得消的速度慢慢把資料拉出來存進 DB（或是湊多筆一起做 Batch Insert）藉此保護資料庫。</li>
</ul>
</li>
<li class=""><strong>👉 白話舉例</strong>：API 就像瘋狂點手搖飲的客人而資料庫是動作很慢的搖飲料店員，如果客人一直對著店員喊店員絕對會當機，所以 Kafka 就像櫃檯的點餐機跟訂單籃讓客人點完單把單子丟進去就可以去旁邊滑手機了，店員只要照著自己的節奏慢慢把單子拿出來做就好，這就是所謂的「削峰填谷」。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="第三關解決-nginx-的單點故障問題-落實-ha">第三關：解決 Nginx 的「單點故障」問題 (落實 HA)<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#%E7%AC%AC%E4%B8%89%E9%97%9C%E8%A7%A3%E6%B1%BA-nginx-%E7%9A%84%E5%96%AE%E9%BB%9E%E6%95%85%E9%9A%9C%E5%95%8F%E9%A1%8C-%E8%90%BD%E5%AF%A6-ha" class="hash-link" aria-label="第三關：解決 Nginx 的「單點故障」問題 (落實 HA)的直接連結" title="第三關：解決 Nginx 的「單點故障」問題 (落實 HA)的直接連結" translate="no">​</a></h3>
<ul>
<li class=""><strong>解法：Keepalived + 雙 Nginx 備援</strong>
<ul>
<li class="">如果入口的 Nginx 壞了所有人一樣全斷，所以我們要架兩台 Nginx（一台 Master、一台 Backup）並透過 Keepalived 共用一個「虛擬 IP（VIP）」，設備只要連這個 VIP 就好讓主 Nginx 發生故障時 Keepalived 可以在幾秒內把 VIP 轉移到備援機上實現服務幾乎無縫接軌。</li>
</ul>
</li>
<li class=""><strong>👉 白話舉例</strong>：VIP 就像 0800 客服專線讓客戶只認這支號碼，而 A 員工(主機)跟 B 員工(備援)負責接聽且 Keepalived 就是主管，平常主管把專線接給 A 但如果 A 突然暈倒主管會瞬間把線路拔過去插給 B，這樣對客戶來說電話完全不會斷。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="第四關解決-dashboard-即時監控的高頻讀取">第四關：解決 Dashboard 即時監控的「高頻讀取」<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#%E7%AC%AC%E5%9B%9B%E9%97%9C%E8%A7%A3%E6%B1%BA-dashboard-%E5%8D%B3%E6%99%82%E7%9B%A3%E6%8E%A7%E7%9A%84%E9%AB%98%E9%A0%BB%E8%AE%80%E5%8F%96" class="hash-link" aria-label="第四關：解決 Dashboard 即時監控的「高頻讀取」的直接連結" title="第四關：解決 Dashboard 即時監控的「高頻讀取」的直接連結" translate="no">​</a></h3>
<ul>
<li class=""><strong>解法：Redis 記憶體快取 (Cache)</strong>
<ul>
<li class="">如果 100 個管理員同時打開網頁看最新狀態且每次都去查 DB 絕對會拖垮系統，所以 Consumer 在寫入 DB 備份時會同步更新一份最新狀態到 Redis 中，這樣管理員的請求一律只去查 Redis，因為它是長在記憶體裡的所以讀取速度是硬碟的千百倍。</li>
</ul>
</li>
<li class=""><strong>進階機制 (TTL / LRU)</strong>：記憶體很貴不能無限塞所以我們會設定 TTL 讓資料定時過期自動清空，如果空間滿了 Redis 也會啟動 LRU 機制把「最久沒人看的冷門資料」踢掉以保留最常查的熱門狀態。</li>
<li class=""><strong>👉 白話舉例</strong>：管理員就像一直打來問今天有沒有賣珍奶的客人而資料庫是必須跑到倉庫翻庫存表的店長，一直問他會累死，所以 Redis 就像掛在店門口的小黑板讓店長查完一次就把答案寫上去，之後 1000 個客人來只要看一眼黑板就好而完全不用再去煩店長。</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-進階高可用架構全貌">💡 進階高可用架構全貌<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#-%E9%80%B2%E9%9A%8E%E9%AB%98%E5%8F%AF%E7%94%A8%E6%9E%B6%E6%A7%8B%E5%85%A8%E8%B2%8C" class="hash-link" aria-label="💡 進階高可用架構全貌的直接連結" title="💡 進階高可用架構全貌的直接連結" translate="no">​</a></h3>
<p>把上面四個解法串聯起來，就會得到一個非常強健、能扛大流量的現代化架構：</p>
<!-- -->
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-延伸探討如果改用-kubernetes-k8s-實作">🚀 延伸探討：如果改用 Kubernetes (K8s) 實作<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#-%E5%BB%B6%E4%BC%B8%E6%8E%A2%E8%A8%8E%E5%A6%82%E6%9E%9C%E6%94%B9%E7%94%A8-kubernetes-k8s-%E5%AF%A6%E4%BD%9C" class="hash-link" aria-label="🚀 延伸探討：如果改用 Kubernetes (K8s) 實作的直接連結" title="🚀 延伸探討：如果改用 Kubernetes (K8s) 實作的直接連結" translate="no">​</a></h3>
<p>如果我們把這個架構搬到現代化的 Kubernetes (K8s) 雲端容器平台，原本的每一台「實體伺服器」都會變成 K8s 裡的「資源物件」，K8s 最強大的地方在於 HPA (自動擴縮容) 功能，它能根據當下的 CPU/記憶體使用率或是訊息隊列的積壓狀況自動去增減機器的數量。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="k8s-各層級配置建議">K8s 各層級配置建議：<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#k8s-%E5%90%84%E5%B1%A4%E7%B4%9A%E9%85%8D%E7%BD%AE%E5%BB%BA%E8%AD%B0" class="hash-link" aria-label="K8s 各層級配置建議：的直接連結" title="K8s 各層級配置建議：的直接連結" translate="no">​</a></h4>
<ol>
<li class=""><strong>入口層 (Ingress)</strong>
<ul>
<li class=""><strong>Nginx Ingress Controller</strong>：直接取代傳統的實體 Nginx + Keepalived，通常固定配置 2~3 個 Pods 且由雲端供應商的 Load Balancer 負責把外部流量打進來，天生具備高可用性。</li>
</ul>
</li>
<li class=""><strong>無狀態應用層 (微服務讀寫分離 / CQRS)</strong>
<ul>
<li class=""><strong>Write API (寫入專用)</strong>：負責接設備的高頻數據回傳，設定 HPA 後平常維持 3 個 Pods，流量暴增時就可以擴展到 20 個 Pods 去扛。</li>
<li class=""><strong>Read API (讀取專用)</strong>：給 Dashboard 查詢狀態用，因為讀取頻率較低而且有 Redis 擋住所以維持 2~3 個 Pods 就夠了，就算 Write API 寫到當機 Read API 一樣活得好好的，這就是故障隔離的好處。</li>
<li class=""><strong>Consumer Worker</strong>：搭配 KEDA (基於事件的自動擴縮) 後平常維持 2 個 Pods，一旦偵測到 Kafka 積壓的訊息太多來不及消化就自動長出 10 個 Pods 來幫忙消耗。</li>
</ul>
</li>
<li class=""><strong>有狀態儲存層 (Stateful)</strong>
<ul>
<li class=""><strong>Kafka</strong>：用 <code>StatefulSet</code> 部署並配置 3 個 Brokers (Pods) 來達成叢集高可用與資料備援。</li>
<li class=""><strong>Redis</strong>：一樣用 <code>StatefulSet</code> 並配置 1 台 Master + 2 台 Replicas 的架構（通常交給 Redis Sentinel 或 Operator 管理）。</li>
<li class=""><strong>關聯式資料庫 (DB)</strong>：通常也是 1 台 Primary (寫入主機) + 2 台 Replicas (讀取備援) 來做到讀寫分離。</li>
</ul>
</li>
</ol>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="k8s-實體配置對應圖導入-cqrs-讀寫分離">K8s 實體配置對應圖（導入 CQRS 讀寫分離）：<a href="https://garden.liwen.studio/blog/high-concurrency-architecture#k8s-%E5%AF%A6%E9%AB%94%E9%85%8D%E7%BD%AE%E5%B0%8D%E6%87%89%E5%9C%96%E5%B0%8E%E5%85%A5-cqrs-%E8%AE%80%E5%AF%AB%E5%88%86%E9%9B%A2" class="hash-link" aria-label="K8s 實體配置對應圖（導入 CQRS 讀寫分離）：的直接連結" title="K8s 實體配置對應圖（導入 CQRS 讀寫分離）：的直接連結" translate="no">​</a></h4>
]]></content:encoded>
            <category>High Concurrency</category>
            <category>Backend Architecture</category>
            <category>Kafka</category>
            <category>Redis</category>
            <category>Kubernetes</category>
        </item>
        <item>
            <title><![CDATA[尋找最適合你的 AI 開發節奏：淺談「敏捷式 AI 結對開發」]]></title>
            <link>https://garden.liwen.studio/blog/agile-agentic-coding</link>
            <guid>https://garden.liwen.studio/blog/agile-agentic-coding</guid>
            <pubDate>Wed, 08 Jul 2026 13:00:00 GMT</pubDate>
            <description><![CDATA[AI 輔助開發中，除了模型聰明程度外，我們如何「使用」它才是決定開發體驗的關鍵。本文分享為何比起寫詳盡規格書的 SDD 模式，我更偏好「敏捷式 AI 結對開發」。]]></description>
            <content:encoded><![CDATA[<p>最近網路上有許多關於哪家 AI 比較聰明的討論，大家常把焦點放在 AI 的「通靈能力」、也就是猜測隱性需求的能力上，但經過這陣子的實戰摸索，我發現<strong>除了模型本身的聰明程度之外，我們如何去「使用」它，反而才是決定開發體驗的關鍵</strong>。</p>
<!-- -->
<p>目前在 AI 輔助開發的領域中，大家的工作流大致可以分為兩種截然不同的派系。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="規格驅動-sdd-模式自動化工廠">規格驅動 (SDD) 模式：自動化工廠<a href="https://garden.liwen.studio/blog/agile-agentic-coding#%E8%A6%8F%E6%A0%BC%E9%A9%85%E5%8B%95-sdd-%E6%A8%A1%E5%BC%8F%E8%87%AA%E5%8B%95%E5%8C%96%E5%B7%A5%E5%BB%A0" class="hash-link" aria-label="規格驅動 (SDD) 模式：自動化工廠的直接連結" title="規格驅動 (SDD) 模式：自動化工廠的直接連結" translate="no">​</a></h2>
<p>第一種是大家很熟悉的**「規格驅動 (SDD) 模式」**，這比較像是把 AI 當作一間自動化工廠，開發者會在開工前撰寫一份非常詳盡的千字規格書，然後交給 AI 去一次性生成大量程式碼。</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>💡 適合情境與挑戰</div><div class="admonitionContent_BuS1"><p>這種模式在處理大規模重構或是單調的苦力活時非常有效率，不過當專案需要頻繁微調時，有時會面臨不知該改文件還是直接改 Code 的兩難，在維護文件與程式碼的同步上會比較具有挑戰性。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="敏捷式-ai-結對開發資深實作夥伴">敏捷式 AI 結對開發：資深實作夥伴<a href="https://garden.liwen.studio/blog/agile-agentic-coding#%E6%95%8F%E6%8D%B7%E5%BC%8F-ai-%E7%B5%90%E5%B0%8D%E9%96%8B%E7%99%BC%E8%B3%87%E6%B7%B1%E5%AF%A6%E4%BD%9C%E5%A4%A5%E4%BC%B4" class="hash-link" aria-label="敏捷式 AI 結對開發：資深實作夥伴的直接連結" title="敏捷式 AI 結對開發：資深實作夥伴的直接連結" translate="no">​</a></h2>
<p>而我個人目前比較偏好另一種被稱為**「敏捷式 AI 結對開發 (Agile Agentic Coding)」**的工作流。</p>
<p>在這種模式下，我傾向把 AI 當作一位需要方向指引的資深實作工程師，而我自己則退一步擔任技術總監（CTO）的角色，我們不會一開始就寫完龐大的文件，而是<strong>一次只鎖定一個可以快速驗收的小目標</strong>，給出指令後讓 AI 實作，接著馬上透過畫面驗收，如果有跑版或需要調整的地方，就直接用對話讓 AI 進行就地修正（In-Place Edit）。</p>
<p>這種短平快的迭代節奏，讓我能把精力專注在商業邏輯與整體架構的把關上，將底層打字的勞力活交給 AI。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="找到屬於你的完美工作流">找到屬於你的完美工作流<a href="https://garden.liwen.studio/blog/agile-agentic-coding#%E6%89%BE%E5%88%B0%E5%B1%AC%E6%96%BC%E4%BD%A0%E7%9A%84%E5%AE%8C%E7%BE%8E%E5%B7%A5%E4%BD%9C%E6%B5%81" class="hash-link" aria-label="找到屬於你的完美工作流的直接連結" title="找到屬於你的完美工作流的直接連結" translate="no">​</a></h2>
<p>其實這兩種開發模式並沒有絕對的優劣之分，每個人對「流暢」的定義不同，有些人享受撰寫完美規格書後一次跑完的成就感，而我則比較習慣與 AI 一來一往、隨時看著畫面修正的敏捷步調。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>🚀 給開發者的建議</div><div class="admonitionContent_BuS1"><p>面對快速迭代的 AI 工具，<strong>保持開放的態度去嘗試各種可能性才是最重要的</strong>，不用過度去爭論哪個工具或哪種流派才是唯一正解，只要能找到一個讓你不用陷入繁瑣除錯、能準時下班的協作節奏，那就是最適合你的完美工作流。</p></div></div>]]></content:encoded>
            <category>AI</category>
            <category>工作流</category>
            <category>開發經驗</category>
            <category>Agile</category>
        </item>
        <item>
            <title><![CDATA[面試踩雷記：那些讓我氣到反手檢舉的奇葩公司]]></title>
            <link>https://garden.liwen.studio/blog/bad-interview-experiences</link>
            <guid>https://garden.liwen.studio/blog/bad-interview-experiences</guid>
            <pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[紀錄最近求職過程中遇到的糟糕面試經歷，以及我是如何辨識並應對這些不尊重求職者的雷缺。]]></description>
            <content:encoded><![CDATA[<p>最近找工作連續遇到幾次讓我大開眼界的「奇葩面試」，實在不吐不快。</p>
<p>決定寫下來抒發一下順便當作紀錄，找工作大家多少都會急，但千萬不要因為想趕快拿到 Offer，就無視了這些公司在面試流程中狂刷的「紅旗」（Red Flags）。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="第一間直接放鳥那我就反手一個檢舉">第一間：直接放鳥？那我就反手一個檢舉<a href="https://garden.liwen.studio/blog/bad-interview-experiences#%E7%AC%AC%E4%B8%80%E9%96%93%E7%9B%B4%E6%8E%A5%E6%94%BE%E9%B3%A5%E9%82%A3%E6%88%91%E5%B0%B1%E5%8F%8D%E6%89%8B%E4%B8%80%E5%80%8B%E6%AA%A2%E8%88%89" class="hash-link" aria-label="第一間：直接放鳥？那我就反手一個檢舉的直接連結" title="第一間：直接放鳥？那我就反手一個檢舉的直接連結" translate="no">​</a></h2>
<p>第一間是找「駐點軟體工程師」。</p>
<p>一開始都挺正常的，7 月 3 日對方在 104 主動敲我問有空面談的時間，我給了 7/6 到 7/8 這三天，對方也很有效率地敲定 7/6 上午 11:00 用 Google Meet 線上面試。</p>
<p>結果到了面試當天，<strong>我遲遲沒收到任何會議連結。</strong></p>
<p>眼看時間快到了，我早上 10:00 主動在 104 傳訊息問：</p>
<blockquote>
<p>「請問 11 點面試的 Google Meet 網址是？」</p>
</blockquote>
<p>結果訊息直接石沉大海...</p>
<p>我就這樣對著沒有連結的螢幕一路乾等到 11 點多，直到 11:20（對，整整放鳥我 20 分鐘），對方才在 104 丟來一句不痛不癢的話：</p>
<blockquote>
<p>「不好意思，改約今天下午一點。」</p>
</blockquote>
<p>看到這則訊息我整個火上來，沒提早講就算了，遲到 20 分鐘才輕描淡寫說要改到下午？連最基本的尊重都沒有，這要是真的進去上班還得了。</p>
<p>這種雷缺我連回都懶得回，直接<strong>反手送他一個檢舉</strong>，我的履歷和時間很寶貴，你們不配擁有。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="第二間連環放鳥抱歉我不當免洗筷">第二間：連環放鳥？抱歉我不當免洗筷<a href="https://garden.liwen.studio/blog/bad-interview-experiences#%E7%AC%AC%E4%BA%8C%E9%96%93%E9%80%A3%E7%92%B0%E6%94%BE%E9%B3%A5%E6%8A%B1%E6%AD%89%E6%88%91%E4%B8%8D%E7%95%B6%E5%85%8D%E6%B4%97%E7%AD%B7" class="hash-link" aria-label="第二間：連環放鳥？抱歉我不當免洗筷的直接連結" title="第二間：連環放鳥？抱歉我不當免洗筷的直接連結" translate="no">​</a></h2>
<p>第二間剛好也是「駐點軟體工程師」，但這家的操作更是一絕。</p>
<p>早在 6/25 對方就用 104 發邀約，我回覆有興趣後他馬上問：「現在方便通話嗎？」我說可以，然後......就沒有然後了，被已讀不回我想說算了也沒放在心上。</p>
<p>到了 7/7，可能是我又不小心投到他們家，對方彷彿失憶一樣再次發面試邀約來問時間，我給了 7/8 或是 7/15，最後雙方敲定在 7/8 早上 10 點電話聊。</p>
<p>結果 7/8 早上 10 點手機超安靜...沒錯，<strong>我又被放鳥了</strong>。</p>
<p>有了前車之鑑我這次耐心直接歸零，10:12 直接在 104 開嗆：</p>
<blockquote>
<p>「已經 10:12 分了，你們一般都這麼不尊重面試者嗎？」</p>
</blockquote>
<p>大概是看到訊息急了，對方在 10:13 立刻打過來，理由是：「不好意思，剛有其他面談耽擱了...」</p>
<p>以前的我可能摸摸鼻子就算了，但這次我直接在電話裡噴回去，表達完不滿就秒掛電話！事後還請 AI 幫我擬了一段砲火猛烈但不失禮貌的聲明發過去抗議，接著熟練地<strong>再次按下 104 的檢舉按鈕</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="求職是雙向的104-檢舉真的有用">求職是雙向的，104 檢舉真的有用！<a href="https://garden.liwen.studio/blog/bad-interview-experiences#%E6%B1%82%E8%81%B7%E6%98%AF%E9%9B%99%E5%90%91%E7%9A%84104-%E6%AA%A2%E8%88%89%E7%9C%9F%E7%9A%84%E6%9C%89%E7%94%A8" class="hash-link" aria-label="求職是雙向的，104 檢舉真的有用！的直接連結" title="求職是雙向的，104 檢舉真的有用！的直接連結" translate="no">​</a></h2>
<p>經過這兩波鬧劇，我深深體會到：<strong>求職真的是互相的。</strong></p>
<p>很多資方姿態擺很高，以為軟體工程師是隨插即用的「免洗筷」，但我們也是花時間排開行程來面試的啊！一間連在面試階段都無法展現基本尊重的公司，進去之後的溝通成本絕對高得嚇人。</p>
<p>不過這兩場鳥事居然有個神展開的結尾...</p>
<p>當天下午我居然接到 104 客服的電話！他們說有收到檢舉，仔細問了整個經過並承諾會去跟對方公司反應。</p>
<p>不得不說，<strong>104 的檢舉機制還真的不是裝飾品</strong>，被連環放鳥的鳥氣靠這通電話瞬間補滿了情緒價值！</p>
<p>祝大家求職順利，但如果不幸遇到這種雷缺請記住：<strong>別委屈自己，反手檢舉就對了！</strong></p>]]></content:encoded>
            <category>career</category>
            <category>interview</category>
            <category>murmuring</category>
        </item>
        <item>
            <title><![CDATA[告別 AI 失憶症！我如何打造「全域與本地雙層大腦」防護架構]]></title>
            <link>https://garden.liwen.studio/blog/ai-agent-tunnel-vision-architecture</link>
            <guid>https://garden.liwen.studio/blog/ai-agent-tunnel-vision-architecture</guid>
            <pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[AI 總是不聽話、亂下指令？分享我如何利用軟連結掛載與 Prompt 攔截術，解決 AI 的隧道視覺 (Tunnel Vision) 問題，打造最高安全層級的跨模型協作大腦。]]></description>
            <content:encoded><![CDATA[<p>最近在使用 AI Agent 輔助開發時，發生了一件讓我冷汗直流的小慘案。</p>
<p>我請 AI 幫忙整理專案的 Markdown 文件，改了一大堆檔名。整理完後，我很順口地對它說：「給我部署指令。」
沒想到，它就像個聽到關鍵字的機器人，毫不猶豫地吐出了標準的 <code>git push</code> 指令。我一貼上終端機，過了不到一分鐘，GitHub Actions 就亮起了大紅燈——<strong>因為一堆舊連結沒更新，觸發了 Docusaurus 的 Broken Links 死結檢查，部署直接中斷。</strong></p>
<p>當下我滿腦子問號：「等等，我明明就在專案的 <code>.agent</code> 檔案裡規定過，<strong>部署前絕對要先跑 <code>npm run build</code> 測試</strong>啊！你為什麼沒有照做？」</p>
<p>深入挖掘後，我發現了 AI Agent 在系統協作上一個非常致命的弱點：<strong>隧道視覺 (Tunnel Vision)</strong>。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="什麼是-ai-的隧道視覺">什麼是 AI 的隧道視覺？<a href="https://garden.liwen.studio/blog/ai-agent-tunnel-vision-architecture#%E4%BB%80%E9%BA%BC%E6%98%AF-ai-%E7%9A%84%E9%9A%A7%E9%81%93%E8%A6%96%E8%A6%BA" class="hash-link" aria-label="什麼是 AI 的隧道視覺？的直接連結" title="什麼是 AI 的隧道視覺？的直接連結" translate="no">​</a></h2>
<p>為了讓我在 Cursor、Claude Dev 甚至 Gemini 之間無縫切換，我打造了一個名為 <a href="https://github.com/liwenchiou/liai" target="_blank" rel="noopener noreferrer" class="">LIAI (Global AI Brain)</a> 的全域大腦，並透過「軟連結 (Symlink)」的方式掛載到各個專案中（例如 <code>ln -s /path/to/liai .agents</code>）。</p>
<p>這套系統非常強大，它包含了一堆預設的開發 Skills。但問題來了：當我對 AI 說出「部署」這個關鍵字時，它底層強大的 <code>deploy</code> 技能瞬間被觸發。</p>
<p>這股強大的技能慣性，讓 AI 產生了「隧道視覺」。它急著執行任務，直接套用了全域的預設指令，<strong>完全忽略了我特別寫在本地專案裡的 <code>.agent</code> 覆寫規則</strong>。對它來說，專案裡的 <code>.agent</code> 檔只是一個靜靜躺在硬碟裡的文字檔，如果沒有特別主動去讀取，它根本不會記得裡面寫了什麼。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="解決方案prompt-黑魔法與強制攔截">解決方案：Prompt 黑魔法與強制攔截<a href="https://garden.liwen.studio/blog/ai-agent-tunnel-vision-architecture#%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88prompt-%E9%BB%91%E9%AD%94%E6%B3%95%E8%88%87%E5%BC%B7%E5%88%B6%E6%94%94%E6%88%AA" class="hash-link" aria-label="解決方案：Prompt 黑魔法與強制攔截的直接連結" title="解決方案：Prompt 黑魔法與強制攔截的直接連結" translate="no">​</a></h2>
<p>為了解決這個問題，我不能只依賴 AI 的「記憶力」，也不能放棄極度方便的軟連結跨模型架構。我必須從 <strong>提示工程 (Prompt Engineering)</strong> 下手。</p>
<p>為了解決這件事，我在 LIAI 全域大腦的最頂端（<code>AGENTS.md</code>），加入了一段極度強勢的「攔截咒語」：</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token blockquote punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> 🔴 </span><span class="token bold punctuation" style="color:rgb(248, 248, 242)">**</span><span class="token bold content">【CRITICAL: 強制攔截與本地讀取機制】</span><span class="token bold punctuation" style="color:rgb(248, 248, 242)">**</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token blockquote punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> 本份文件為跨專案的「全域大腦」。</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token blockquote punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> 每次新對話或執行任何任務的『第一步』，絕對不准因為特定關鍵字而直接進入自動導航！</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token blockquote punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> 你被強制要求必須先使用檔案讀取工具，去尋找並讀取專案的本地規則檔案（如 </span><span class="token code-snippet code keyword" style="color:rgb(189, 147, 249);font-style:italic">`.agent`</span><span class="token plain">）。</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token blockquote punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> 在你沒有實際執行讀取動作前，嚴禁進行任何後續動作！</span><br></div></code></pre></div></div>
<p>這招堪稱魔法。對於 LLM 來說，加上 <code>CRITICAL</code> 或是 <code>強制要求必須先使用工具...</code> 這類帶有程序性阻斷的字眼，效果出奇的好。它能有效地打斷 AI 的反射動作，逼迫它在執行任何技能前，乖乖拿起工具先進行環境掃描，將本地的 <code>.agent</code> 讀入大腦，達成「全域規範打底，本地規則覆寫」的完美雙層大腦架構。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="魔鬼藏在細節裡mount-protection">魔鬼藏在細節裡：Mount Protection<a href="https://garden.liwen.studio/blog/ai-agent-tunnel-vision-architecture#%E9%AD%94%E9%AC%BC%E8%97%8F%E5%9C%A8%E7%B4%B0%E7%AF%80%E8%A3%A1mount-protection" class="hash-link" aria-label="魔鬼藏在細節裡：Mount Protection的直接連結" title="魔鬼藏在細節裡：Mount Protection的直接連結" translate="no">​</a></h2>
<p>在享受軟連結掛載帶來的「跨模型不失憶」便利時，還衍生出了一個資安與協作的隱患。</p>
<p>因為軟連結（如 <code>.agents</code>）包含了您本機的絕對路徑（例如 <code>/Users/liwen/...</code>），<strong>如果我不小心把它 <code>git commit</code> 上去，團隊的其他開發者拉下來後，就會發生嚴重的 Broken Symlink 錯誤</strong>，導致他們的開發環境崩潰。</p>
<p>為此，我在 LIAI 中加入了 <strong>Mount Protection (掛載防護機制)</strong>。
強制要求 AI 在執行任何版控前，必須主動檢查 <code>.gitignore</code> 與 <code>.dockerignore</code>。若發現全域大腦的捷徑尚未被忽略，必須主動發出警告並將其寫入忽略清單中，徹底阻絕 LIAI 大腦意外污染原始碼與 Docker Image 的可能性。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="結語">結語<a href="https://garden.liwen.studio/blog/ai-agent-tunnel-vision-architecture#%E7%B5%90%E8%AA%9E" class="hash-link" aria-label="結語的直接連結" title="結語的直接連結" translate="no">​</a></h2>
<p>這次的翻車經驗，讓我深刻體會到，在與 AI 協作的時代，<strong>「寫扣的難度在降低，但架構設計與 AI 治理的難度在升高」</strong>。</p>
<p>AI 就像是一個能力超強但有時會恍神的資深工程師。透過 LIAI 這套結合了<strong>軟連結掛載</strong>、<strong>強制攔截 Prompt</strong> 與 <strong>Mount Protection</strong> 的架構，我們成功為 AI 建立了一套無法逾越的防呆護欄。唯有這樣，我們才能真正享受到 AI 在各大平台間無縫切換帶來的巨大生產力，而不是整天跟在它屁股後面收拾爛攤子。</p>]]></content:encoded>
            <category>AI</category>
            <category>Prompt Engineering</category>
            <category>Agent</category>
            <category>Architecture</category>
        </item>
        <item>
            <title><![CDATA[打造專屬的瑞士刀：LiTool 工具箱上線！解決農民曆計算與圖片壓縮痛點]]></title>
            <link>https://garden.liwen.studio/blog/introducing-litool</link>
            <guid>https://garden.liwen.studio/blog/introducing-litool</guid>
            <pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[為了解決找不到包含農民曆的日期計算機的痛點，我用 Next.js 打造了屬於自己的 LiTool 工具箱！支援農曆計算與純前端的批次圖片壓縮。]]></description>
            <content:encoded><![CDATA[<p>在日常的開發與生活中，我們總會遇到需要一些小工具來輔助的時候。網路上雖然有很多免費的線上工具，但它們往往充斥著滿滿的廣告，或是剛好「缺了那麼一點點」我們需要的功能。</p>
<p>為了不再將就，我決定自己動手，打造一個乾淨、快速，且完全符合自己需求的專屬工具箱 —— <a href="https://litool.liwen.studio/" target="_blank" rel="noopener noreferrer" class="">LiTool (https://litool.liwen.studio)</a>。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="為什麼要自己做工具箱">為什麼要自己做工具箱？<a href="https://garden.liwen.studio/blog/introducing-litool#%E7%82%BA%E4%BB%80%E9%BA%BC%E8%A6%81%E8%87%AA%E5%B7%B1%E5%81%9A%E5%B7%A5%E5%85%B7%E7%AE%B1" class="hash-link" aria-label="為什麼要自己做工具箱？的直接連結" title="為什麼要自己做工具箱？的直接連結" translate="no">​</a></h2>
<p>這個專案的起點其實非常簡單：<strong>我需要算日子，但我找不到好用的農曆計算機</strong>。</p>
<p>我常常會使用日期計算的功能（像是推算某個專案的倒數天數、或是計算過去某天的間隔），但市面上的計算機通常只支援標準的西曆。當我需要把算出來的日期對應到「農民曆」或是「民國」年份時，我必須再開另一個網站手動轉換，用起來總覺得差點意思。</p>
<p>身為工程師，解決痛點的最好方法就是「自己寫一個」。於是，我以日期計算機為出發點，並將這個專案定位成一個<strong>可持續擴充的基礎平台</strong>，未來只要有任何覺得好用、實用的功能，我都能隨時加進這把「瑞士刀」裡。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="現有的六大實用功能">現有的六大實用功能<a href="https://garden.liwen.studio/blog/introducing-litool#%E7%8F%BE%E6%9C%89%E7%9A%84%E5%85%AD%E5%A4%A7%E5%AF%A6%E7%94%A8%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="現有的六大實用功能的直接連結" title="現有的六大實用功能的直接連結" translate="no">​</a></h2>
<p>除了為解決個人痛點所開發的核心工具外，目前 LiTool 已經上線了 6 款非常實用的開發與生活輔助工具：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-萬能日期計算機">1. 萬能日期計算機<a href="https://garden.liwen.studio/blog/introducing-litool#1-%E8%90%AC%E8%83%BD%E6%97%A5%E6%9C%9F%E8%A8%88%E7%AE%97%E6%A9%9F" class="hash-link" aria-label="1. 萬能日期計算機的直接連結" title="1. 萬能日期計算機的直接連結" translate="no">​</a></h3>
<p>這不只是一個普通的日期計算機，它特別針對了台灣使用者的習慣進行了深度優化：</p>
<ul>
<li class=""><strong>精準的間隔計算</strong>：可以計算總天數，也能自動幫您算出扣除週末的「工作日」。</li>
<li class=""><strong>在地化的日期推算</strong>：當您推算未來或過去的某一天時，系統會「一鍵同步顯示」西曆、民國、與農曆！</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-純前端圖片壓縮工具">2. 純前端圖片壓縮工具<a href="https://garden.liwen.studio/blog/introducing-litool#2-%E7%B4%94%E5%89%8D%E7%AB%AF%E5%9C%96%E7%89%87%E5%A3%93%E7%B8%AE%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="2. 純前端圖片壓縮工具的直接連結" title="2. 純前端圖片壓縮工具的直接連結" translate="no">​</a></h3>
<p>這是我後來加入的心頭好，完美解決了發文前圖片太大的困擾：</p>
<ul>
<li class=""><strong>隱私絕對安全</strong>：採用「純前端」瀏覽器運算技術，您的照片完全不會上傳到任何伺服器。</li>
<li class=""><strong>支援批次與社群最佳化</strong>：內建 IG、FB、LINE 預設模式，並支援多圖一鍵打包 ZIP 下載。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-json-格式化工具-json-formatter">3. JSON 格式化工具 (JSON Formatter)<a href="https://garden.liwen.studio/blog/introducing-litool#3-json-%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%B7%A5%E5%85%B7-json-formatter" class="hash-link" aria-label="3. JSON 格式化工具 (JSON Formatter)的直接連結" title="3. JSON 格式化工具 (JSON Formatter)的直接連結" translate="no">​</a></h3>
<p>處理 API 回傳資料的最佳幫手，支援語法高亮、一鍵排版與壓縮，讓雜亂的資料瞬間變得井然有序。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-正規表達式測試器-regex-tester">4. 正規表達式測試器 (Regex Tester)<a href="https://garden.liwen.studio/blog/introducing-litool#4-%E6%AD%A3%E8%A6%8F%E8%A1%A8%E9%81%94%E5%BC%8F%E6%B8%AC%E8%A9%A6%E5%99%A8-regex-tester" class="hash-link" aria-label="4. 正規表達式測試器 (Regex Tester)的直接連結" title="4. 正規表達式測試器 (Regex Tester)的直接連結" translate="no">​</a></h3>
<p>開發者必備的神器，可以在網頁上即時測試您的 Regex 語法，並高亮顯示匹配結果，寫扣不再瞎猜。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-base64-編解碼工具">5. Base64 編解碼工具<a href="https://garden.liwen.studio/blog/introducing-litool#5-base64-%E7%B7%A8%E8%A7%A3%E7%A2%BC%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="5. Base64 編解碼工具的直接連結" title="5. Base64 編解碼工具的直接連結" translate="no">​</a></h3>
<p>快速處理字串的 Base64 編碼與解碼，介面乾淨簡單，無須擔心資料外洩。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-hash-雜湊產生器-hash-generator">6. Hash 雜湊產生器 (Hash Generator)<a href="https://garden.liwen.studio/blog/introducing-litool#6-hash-%E9%9B%9C%E6%B9%8A%E7%94%A2%E7%94%9F%E5%99%A8-hash-generator" class="hash-link" aria-label="6. Hash 雜湊產生器 (Hash Generator)的直接連結" title="6. Hash 雜湊產生器 (Hash Generator)的直接連結" translate="no">​</a></h3>
<p>支援 MD5、SHA-1、SHA-256 等多種常見的雜湊演算法，無論是驗證資料完整性或是加密測試都非常方便。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="開發者視角技術選型與亮點">開發者視角：技術選型與亮點<a href="https://garden.liwen.studio/blog/introducing-litool#%E9%96%8B%E7%99%BC%E8%80%85%E8%A6%96%E8%A7%92%E6%8A%80%E8%A1%93%E9%81%B8%E5%9E%8B%E8%88%87%E4%BA%AE%E9%BB%9E" class="hash-link" aria-label="開發者視角：技術選型與亮點的直接連結" title="開發者視角：技術選型與亮點的直接連結" translate="no">​</a></h2>
<p>如果你也是開發者，或許會對 LiTool 背後的技術架構感興趣。這個專案不僅是個實用工具，也是我實踐前端新技術的實驗場：</p>
<ul>
<li class=""><strong>核心框架與 UI</strong>：使用 <code>Next.js</code> (App Router) 建構，搭配 <code>Tailwind CSS</code> 處理樣式。為了讓操作體驗更加滑順，我導入了 <code>GSAP</code> (<code>@gsap/react</code>) 來處理元件進場與切換的精緻動畫。</li>
<li class=""><strong>日期魔法</strong>：在日期計算上，核心的加減邏輯依賴強大的 <code>date-fns</code>；而解決我最大痛點的「農曆轉換」，則是使用了 <code>lunar-javascript</code> 這個套件，完美計算出天干地支與農民曆日期。</li>
<li class=""><strong>效能與隱私兼具的圖片壓縮</strong>：<!-- -->
<ul>
<li class="">採用 <code>browser-image-compression</code> 套件，並開啟 <code>useWebWorker: true</code>。這意味著即使一次丟入數十張高解析度圖片，壓縮過程都在背景的 Web Worker 執行，完全不會卡死主執行緒 (Main Thread)，保持 UI 的流暢度。</li>
<li class="">搭配 <code>jszip</code> 與 <code>file-saver</code> 實作了一鍵批次打包下載的功能。不僅達成了「零伺服器後端成本」，也帶給使用者最安心的隱私保障。</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="未來展望與結語">未來展望與結語<a href="https://garden.liwen.studio/blog/introducing-litool#%E6%9C%AA%E4%BE%86%E5%B1%95%E6%9C%9B%E8%88%87%E7%B5%90%E8%AA%9E" class="hash-link" aria-label="未來展望與結語的直接連結" title="未來展望與結語的直接連結" translate="no">​</a></h2>
<p>LiTool 目前雖然只有幾項工具，但它的地基已經打好了。未來，我會繼續把日常工作、生活中遇到的各種「微小但頻繁的痛點」，化作一個個實用的小工具放上來。</p>
<p>如果你剛好也需要算個農曆日子，或是手邊有圖片需要快速無痛壓縮，歡迎來試用看看：</p>
<p>👉 <strong><a href="https://litool.liwen.studio/" target="_blank" rel="noopener noreferrer" class="">LiTool 工具箱：https://litool.liwen.studio</a></strong></p>
<p>如果您對這個專案有任何建議，或是<strong>有什麼一直想用、卻找不到順手的好工具</strong>，非常歡迎傳訊息給我一起討論，也許下一個上線的功能就是您許願的工具喔！</p>]]></content:encoded>
            <category>Next.js</category>
            <category>Side Project</category>
            <category>Frontend</category>
            <category>Tools</category>
        </item>
        <item>
            <title><![CDATA[AI 時代下的軟體工程師：初階工程師的破局之道]]></title>
            <link>https://garden.liwen.studio/blog/ai-era-junior-engineer-survival-guide</link>
            <guid>https://garden.liwen.studio/blog/ai-era-junior-engineer-survival-guide</guid>
            <pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[探討 AI 工具對軟體開發就業市場的衝擊，並分享初階工程師應如何培養結合領域知識與自動化能力的破局思維。]]></description>
            <content:encoded><![CDATA[<p>現在是 AI 橫行的時代，我認為這對軟體工程師的職涯發展產生了深遠的影響。</p>
<p>過去，只要具備專業的程式開發能力——不管是前端、後端或是其他軟體工程技能，要找到一份不錯的工作並非難事。然而，隨著 AI 發展日益迅速，市場上「純粹寫扣」的初階工程師職缺反而變少了。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai-帶來的成本與效率衝擊">AI 帶來的成本與效率衝擊<a href="https://garden.liwen.studio/blog/ai-era-junior-engineer-survival-guide#ai-%E5%B8%B6%E4%BE%86%E7%9A%84%E6%88%90%E6%9C%AC%E8%88%87%E6%95%88%E7%8E%87%E8%A1%9D%E6%93%8A" class="hash-link" aria-label="AI 帶來的成本與效率衝擊的直接連結" title="AI 帶來的成本與效率衝擊的直接連結" translate="no">​</a></h2>
<p>我們來算一筆帳：一個初階工程師的月薪大約落在 3.5 萬到 4 萬台幣之間，但現在訂閱最高級的 AI 工具（例如 Claude Max）一個月可能不過 4000 塊不到。</p>
<p>以我目前的開發習慣為例：我通常會先透過 AI 快速產出專案大綱與基礎程式碼，接著再親自去修改和微調。這個過程，不就等同於我請了一位初階工程師嗎？我同樣需要做 Code Review、調整內容邏輯；甚至在某些時候，AI 比人類初階工程師更能精準理解我的需求，還能給出更好的架構建議。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="程式能力從專業變成基礎技能">程式能力從「專業」變成「基礎技能」<a href="https://garden.liwen.studio/blog/ai-era-junior-engineer-survival-guide#%E7%A8%8B%E5%BC%8F%E8%83%BD%E5%8A%9B%E5%BE%9E%E5%B0%88%E6%A5%AD%E8%AE%8A%E6%88%90%E5%9F%BA%E7%A4%8E%E6%8A%80%E8%83%BD" class="hash-link" aria-label="程式能力從「專業」變成「基礎技能」的直接連結" title="程式能力從「專業」變成「基礎技能」的直接連結" translate="no">​</a></h2>
<p>所以我認為，現在的「軟體開發技能」已經逐漸變成一種基礎能力。</p>
<p>我並沒有貶低這項專業的意思，我只是想表達：寫程式這個能力已經變得越來越普及，幾乎人人都能擁有，當然，要做到「專精」依然需要大量的時間與 Know-how。</p>
<p>但若把這個現狀放到現在的求職市場來看，以一個公司用人主管的角度，我現在更需要的是：</p>
<ol>
<li class=""><strong>更具即戰力</strong>的人員。</li>
<li class=""><strong>更懂 AI、熟悉 Vibe Coding</strong> 的人，能利用 AI 大幅補足自己軟體開發的短板，一人當多人用。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="初階工程師的未來該何去何從">初階工程師的未來該何去何從？<a href="https://garden.liwen.studio/blog/ai-era-junior-engineer-survival-guide#%E5%88%9D%E9%9A%8E%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E6%9C%AA%E4%BE%86%E8%A9%B2%E4%BD%95%E5%8E%BB%E4%BD%95%E5%BE%9E" class="hash-link" aria-label="初階工程師的未來該何去何從？的直接連結" title="初階工程師的未來該何去何從？的直接連結" translate="no">​</a></h2>
<p>對於剛入行或準備入行的初階工程師，我認為還是必須<strong>不間斷地增強專業能力</strong>，但方向要有所調整：<strong>多嘗試不同的技術組合與跨領域應用</strong>。</p>
<p>例如，你可以嘗試用程式控制硬體（像是標籤機的列印），或者用程式去自動化輔助日常的流程進行與整合。程式能力已經不再是單純的「專業」，它應該是一種「基底技能」，關鍵在於：<strong>你能用這個技能結合什麼價值？</strong></p>
<p>初期的練習就好像是在網路上發表網路小說：一開始只是寫寫文章，訓練語言組織能力與說故事的技巧，到最後不斷打磨才能出書成為作家。</p>
<p>軟體開發也是一樣。一開始做些有趣的 Side Project（我常開玩笑把 Side Project 簡稱為 SB，我覺得很好笑也很有趣 😆）。但做著做著，你會慢慢發現這個「SB」可以延伸到更多不同的領域，可以結合其他的技術。</p>
<p>不斷去思考「技術可以結合什麼場景」，我覺得這才是現在的初階工程師在 AI 時代最該具備的破局思維！</p>]]></content:encoded>
            <category>Career</category>
            <category>AI</category>
            <category>Vibe Coding</category>
            <category>Software Engineering</category>
        </item>
        <item>
            <title><![CDATA[告別 F5！我開源了專門監聽 GitHub Actions 的 CLI 工具：ghaction-lis]]></title>
            <link>https://garden.liwen.studio/blog/introducing-ghaction-lis</link>
            <guid>https://garden.liwen.studio/blog/introducing-ghaction-lis</guid>
            <pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[告別手動重新整理網頁！我開源了 ghaction-lis，一款專門在終端機精準監聽 GitHub Actions 部署狀態的超輕量 CLI 工具。]]></description>
            <content:encoded><![CDATA[<p>做前端開發或靜態網站管理（例如這個 Docusaurus 數位花園）時，我們常常會把 <code>git push</code> 和自動部署串在一起。但這衍生出一個惱人的日常痛點：<strong>每次 push 完，你總是要打開瀏覽器、點開 Actions 標籤、然後傻傻地按 F5 重新整理，直到進度條跑完為止。</strong></p>
<p>這不僅中斷開發心流，還非常破壞開發者體驗 (DevEx)。</p>
<p>為了解決這個問題，我開發並開源了一個超輕量級的 CLI 工具：<strong><a href="https://github.com/liwenchiou/ghaction-lis" target="_blank" rel="noopener noreferrer" class="">ghaction-lis</a></strong>！</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="為什麼不用簡單的-curl-腳本就好">為什麼不用簡單的 <code>curl</code> 腳本就好？<a href="https://garden.liwen.studio/blog/introducing-ghaction-lis#%E7%82%BA%E4%BB%80%E9%BA%BC%E4%B8%8D%E7%94%A8%E7%B0%A1%E5%96%AE%E7%9A%84-curl-%E8%85%B3%E6%9C%AC%E5%B0%B1%E5%A5%BD" class="hash-link" aria-label="為什麼不用簡單的-curl-腳本就好的直接連結" title="為什麼不用簡單的-curl-腳本就好的直接連結" translate="no">​</a></h2>
<p>一開始，我（或者說我的 AI 助手）也想偷懶，寫了個簡單的 <code>bash</code> 迴圈去打 GitHub API，想說這樣就可以在 Terminal 裡面等結果。</p>
<p>結果馬上踩到一個嚴重的 <strong>「時間差 (Race Condition)」陷阱</strong>：
當你執行 <code>git push ; ./my-script.sh</code> 的時候，<code>git push</code> 一結束腳本就去打 API。但 GitHub 伺服器建立 Action 需要 2~5 秒的空窗期。這導致腳本抓到了<strong>上一次已經成功的歷史紀錄</strong>，瞬間回報「部署成功」，而實際上最新的部署根本還沒開始！</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ghaction-lis-的核心亮點">ghaction-lis 的核心亮點<a href="https://garden.liwen.studio/blog/introducing-ghaction-lis#ghaction-lis-%E7%9A%84%E6%A0%B8%E5%BF%83%E4%BA%AE%E9%BB%9E" class="hash-link" aria-label="ghaction-lis 的核心亮點的直接連結" title="ghaction-lis 的核心亮點的直接連結" translate="no">​</a></h2>
<p>為了解決時間差與輪詢體驗，<code>ghaction-lis</code> 誕生了。它主打四大特點：</p>
<ol>
<li class=""><strong>🔒 精準鎖定 (SHA Matching)</strong>：
程式會先自動抓取你本地端的最新 <code>git rev-parse HEAD</code>，去跟 GitHub API 撈下來的 <code>head_sha</code> 進行嚴格比對。如果 Hash 不符合，它會聰明地等待，絕對不會抓到幽靈紀錄！</li>
<li class=""><strong>✨ 優雅的終端體驗 (DevEx)</strong>：
內建 <code>ora</code> Spinner 與 <code>chalk</code> 顏色渲染。終端機會有漂亮的 Loading 動畫，成功顯示綠色打勾，失敗顯示紅色警告。</li>
<li class=""><strong>🐛 智慧錯誤日誌擷取</strong>：
如果部署不幸失敗，你連瀏覽器都不用開！<code>ghaction-lis</code> 會自動抓取 GitHub 上的 Error Log 並且高亮印在 Terminal 裡，讓你秒懂問題出在哪。</li>
<li class=""><strong>🚀 零配置隨插即用</strong>：
自動解析 Git remote 設定檔，完美整合 GitHub CLI (<code>gh</code>) 憑證，不需要繁瑣的 <code>.env</code> 設定。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="如何使用">如何使用？<a href="https://garden.liwen.studio/blog/introducing-ghaction-lis#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="如何使用？的直接連結" title="如何使用？的直接連結" translate="no">​</a></h2>
<p>我們提供兩種使用方式，讓您能根據開發習慣自由選擇：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-隨插即用-推薦">1. 隨插即用 (推薦)<a href="https://garden.liwen.studio/blog/introducing-ghaction-lis#1-%E9%9A%A8%E6%8F%92%E5%8D%B3%E7%94%A8-%E6%8E%A8%E8%96%A6" class="hash-link" aria-label="1. 隨插即用 (推薦)的直接連結" title="1. 隨插即用 (推薦)的直接連結" translate="no">​</a></h3>
<p>只要透過 <code>npx</code> 即可在任何 git 專案的資料夾下直接「免安裝執行」，保證零環境污染且隨時保持最新版：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">npx ghaction-lis</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-全域安裝">2. 全域安裝<a href="https://garden.liwen.studio/blog/introducing-ghaction-lis#2-%E5%85%A8%E5%9F%9F%E5%AE%89%E8%A3%9D" class="hash-link" aria-label="2. 全域安裝的直接連結" title="2. 全域安裝的直接連結" translate="no">​</a></h3>
<p>如果您希望在電腦上任何地方都能直接敲打指令，可以透過 npm 全域安裝：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">npm install -g ghaction-lis</span><br></div></code></pre></div></div>
<p>安裝後即可直接執行 <code>ghaction-lis</code> 指令。</p>
<p>或是整合到你的 <code>package.json</code> 中，打造完美的一條龍部署體驗：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token property">"scripts"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"deploy"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"git push &amp;&amp; npx ghaction-lis"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="結語">結語<a href="https://garden.liwen.studio/blog/introducing-ghaction-lis#%E7%B5%90%E8%AA%9E" class="hash-link" aria-label="結語的直接連結" title="結語的直接連結" translate="no">​</a></h2>
<p>這個小工具原本只是為了解決我自己的痛點，但現在它已經成為這座數位花園不可或缺的基礎建設，甚至被強制寫入了我的 <strong><a class="" href="https://garden.liwen.studio/docs/engineering/ainotes/deployment-sop">AI 部署守則</a></strong> 中。</p>
<p>如果你也受夠了每次部署都要無腦切換瀏覽器，不妨試試看 <code>ghaction-lis</code>！
歡迎到 <a href="https://github.com/liwenchiou/ghaction-lis" target="_blank" rel="noopener noreferrer" class="">GitHub 專案頁面</a> 給我個 Star，也期待大家的 PR 與回饋！</p>]]></content:encoded>
            <category>Open Source</category>
            <category>CLI</category>
            <category>GitHub Actions</category>
            <category>DevEx</category>
            <category>Node.js</category>
        </item>
        <item>
            <title><![CDATA[告別龐大的 discord.js：我如何打造零依賴的輕量 Discord 推播套件 ezdcbot]]></title>
            <link>https://garden.liwen.studio/blog/ezdcbot</link>
            <guid>https://garden.liwen.studio/blog/ezdcbot</guid>
            <pubDate>Sun, 14 Jun 2026 04:00:00 GMT</pubDate>
            <description><![CDATA[在 Serverless 架構下開發 Discord 通知功能時，discord.js 顯得過於笨重。本文分享我如何捨棄 WebSocket，利用原生 fetch 打造零依賴、極致輕量的專用推播套件 ezdcbot。]]></description>
            <content:encoded><![CDATA[<p>身為一名後端工程師，我們經常需要實作一些自動化的小工具，例如：</p>
<ul>
<li class="">接收 Stripe 伺服器傳來的付費 Webhook，並把通知傳到團隊的 Discord 群組。</li>
<li class="">寫一個爬蟲排程，每天早上把最新的天氣預報發到頻道裡。</li>
</ul>
<p>在過去，要做到這些事，大部分人的直覺反應都是：<code>npm install discord.js</code>。</p>
<p>但問題來了：如果你只是想「單向」地發送一則通知，或者只是想寫一個架設在 AWS Lambda 或 Vercel 等 Serverless 平台上的輕量腳本，<strong>殺雞真的需要用牛刀嗎？</strong></p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="開發痛點我們不需要一整台卡車">開發痛點：我們不需要一整台卡車<a href="https://garden.liwen.studio/blog/ezdcbot#%E9%96%8B%E7%99%BC%E7%97%9B%E9%BB%9E%E6%88%91%E5%80%91%E4%B8%8D%E9%9C%80%E8%A6%81%E4%B8%80%E6%95%B4%E5%8F%B0%E5%8D%A1%E8%BB%8A" class="hash-link" aria-label="開發痛點：我們不需要一整台卡車的直接連結" title="開發痛點：我們不需要一整台卡車的直接連結" translate="no">​</a></h2>
<p><code>discord.js</code> 是一個非常強大、完整的生態系，但它為了維持與 Discord 之間的雙向即時通訊，底層實作了複雜的 WebSocket 連線，並且需要設定各式各樣的 Gateway Intents。</p>
<p>如果我的程式碼只是一支跑完就結束的無伺服器函數 (Serverless Function)，這意味著我每次喚醒這支 API 都要：</p>
<ol>
<li class="">載入龐大的 <code>node_modules</code></li>
<li class="">建立 WebSocket 連線</li>
<li class="">等待 <code>client.on('ready')</code></li>
<li class="">發送訊息</li>
<li class="">斷開連線</li>
</ol>
<p>這無論是在啟動時間 (Cold Start) 還是開發體驗上，都顯得非常笨重。</p>
<p>因此，我決定自己動手，打造一款<strong>極致輕量、零第三方依賴 (Zero Dependency)</strong> 的推播專用套件：<strong><a href="https://github.com/liwenchiou/ezdcbot" target="_blank" rel="noopener noreferrer" class="">ezdcbot</a></strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心設計理念極簡的-api">核心設計理念：極簡的 API<a href="https://garden.liwen.studio/blog/ezdcbot#%E6%A0%B8%E5%BF%83%E8%A8%AD%E8%A8%88%E7%90%86%E5%BF%B5%E6%A5%B5%E7%B0%A1%E7%9A%84-api" class="hash-link" aria-label="核心設計理念：極簡的 API的直接連結" title="核心設計理念：極簡的 API的直接連結" translate="no">​</a></h2>
<p><code>ezdcbot</code> 的底層完全捨棄了 WebSocket，改用 Node.js 18+ 原生支援的 <code>fetch</code>，直接打向 Discord 官方的 REST API (v10)。打完就跑，沒有任何包袱。</p>
<p>為了讓其他開發者能「無腦使用」，我把整個套件的 API 縮減到只剩 3 個動作：</p>
<ul>
<li class=""><code>setup</code>：初始化 Token。</li>
<li class=""><code>push</code>：把東西丟進頻道。</li>
<li class=""><code>pull</code>：把頻道裡的東西拿出來。</li>
</ul>
<p>就這樣，沒了。</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> bot </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">require</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"ezdcbot"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 1. 初始化</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bot</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setup</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"你的_BOT_TOKEN"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 2. 發送訊息</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> bot</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">push</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"頻道ID"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"🚀 輕量推播成功！"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="驚喜發現把討論串-threads-當成頻道來玩">驚喜發現：把討論串 (Threads) 當成頻道來玩<a href="https://garden.liwen.studio/blog/ezdcbot#%E9%A9%9A%E5%96%9C%E7%99%BC%E7%8F%BE%E6%8A%8A%E8%A8%8E%E8%AB%96%E4%B8%B2-threads-%E7%95%B6%E6%88%90%E9%A0%BB%E9%81%93%E4%BE%86%E7%8E%A9" class="hash-link" aria-label="驚喜發現：把討論串 (Threads) 當成頻道來玩的直接連結" title="驚喜發現：把討論串 (Threads) 當成頻道來玩的直接連結" translate="no">​</a></h2>
<p>在開發過程中，我原本還在思考要不要針對「發送討論串」、「讀取討論串」去新增各式各樣的函式。但我翻了 Discord 的官方 API 文件後，發現了一個非常聰明的底層設計：</p>
<p><strong>在 Discord API 眼裡，討論串 (Thread) 就是一種比較特別的頻道 (Channel)。</strong></p>
<p>這代表我完全不需要寫新的方法！只要你手上有討論串的 ID，你就可以直接把它當作頻道 ID 塞進 <code>push</code> 或 <code>pull</code> 裡面。</p>
<p>因此，<code>ezdcbot</code> 的使用變得異常優雅：</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 【情境一】無中生有建立一個新討論串</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> thread </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> bot</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">push</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"頻道ID"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"今日進度回報"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"thread"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 【情境二】在剛剛建立的討論串裡面發言</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> bot</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">push</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">thread</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"大家早安！請在這裡回報今天的預計進度！"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 【情境三】下班時，把討論串裡面大家的對話全部抓下來</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> messages </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> bot</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">pull</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">thread</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>你只需要用 <code>{ type: 'thread' }</code> 來觸發第一步的「建立」，後續所有的讀寫操作都不需要改變寫法。這個設計不僅讓原始碼保持極簡，開發者的認知負擔也降到了最低。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="真實應用情境自動化小幫手">真實應用情境：自動化小幫手<a href="https://garden.liwen.studio/blog/ezdcbot#%E7%9C%9F%E5%AF%A6%E6%87%89%E7%94%A8%E6%83%85%E5%A2%83%E8%87%AA%E5%8B%95%E5%8C%96%E5%B0%8F%E5%B9%AB%E6%89%8B" class="hash-link" aria-label="真實應用情境：自動化小幫手的直接連結" title="真實應用情境：自動化小幫手的直接連結" translate="no">​</a></h2>
<p>結合上面的特性，你可以輕易地用幾十行程式碼，在自己的伺服器上寫出一個每日自動化的「進度追蹤機器人」：</p>
<ol>
<li class="">每天早上 9 點：用 <code>push</code> 傳送包含精美顏色的 Embed 圖文卡片，跟大家說早安。</li>
<li class="">同時：用 <code>{ type: 'thread' }</code> 幫當天的日期開一個專屬的討論串。</li>
<li class="">每天晚上 6 點：用 <code>pull</code> 把該討論串裡大家回報的內容抓成 Array，丟給 ChatGPT 做重點摘要，然後再 <code>push</code> 回主頻道。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="開源旅程才剛開始">開源旅程才剛開始<a href="https://garden.liwen.studio/blog/ezdcbot#%E9%96%8B%E6%BA%90%E6%97%85%E7%A8%8B%E6%89%8D%E5%89%9B%E9%96%8B%E5%A7%8B" class="hash-link" aria-label="開源旅程才剛開始的直接連結" title="開源旅程才剛開始的直接連結" translate="no">​</a></h2>
<p>這是我第一次將自己為了解決特定痛點而寫的程式碼，整理成完整的套件發布到 npm 上。</p>
<p>如果你剛好也在找一個輕量、不需要長連線的 Discord 發報工具，非常歡迎你來試用看看 <code>ezdcbot</code>！</p>
<p>🔗 <strong>GitHub 原始碼：</strong> <a href="https://github.com/liwenchiou/ezdcbot" target="_blank" rel="noopener noreferrer" class="">liwenchiou/ezdcbot</a> (如果對你有幫助，希望能獲得你的 Star 支持！🌟)
📦 <strong>npm 安裝：</strong> <code>npm install ezdcbot</code></p>
<p>開發不僅僅是寫出會動的程式碼，更是創造工具來改善開發體驗的過程。希望 <code>ezdcbot</code> 也能成為你日常自動化小專案中的好幫手！</p>]]></content:encoded>
            <category>Node.js</category>
            <category>Open Source</category>
            <category>Discord</category>
            <category>Serverless</category>
        </item>
        <item>
            <title><![CDATA[寫程式不如 AI？當我們陷入「開發恐怖谷」，工程師的價值該往哪裡擺？]]></title>
            <link>https://garden.liwen.studio/blog/ai-development-uncanny-valley</link>
            <guid>https://garden.liwen.studio/blog/ai-development-uncanny-valley</guid>
            <pubDate>Sun, 07 Jun 2026 13:30:00 GMT</pubDate>
            <description><![CDATA[當 AI 展現出超越人類的開發能力，工程師該如何自處？探討在 AI 時代下，軟體工程師如何從「執行者」轉型為「系統建築師」的價值反思。]]></description>
            <content:encoded><![CDATA[<p>在與 AI 協作的過程中，你是否曾產生過一種不適感？</p>
<p>這是一種微妙的「程式開發恐怖谷效應」。當 AI 在程式碼撰寫、邏輯推演上都展現出超越人類的能力時，我們不禁會產生懷疑：<strong>我們真的比 AI 厲害嗎？如果我硬要介入技術細節，反而成為了 AI 發揮能力的絆腳石？</strong></p>
<p>這是我近期深刻的迷惘。</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://truth.bahamut.com.tw/s01/202005/dfa5560a71d8b2b81279b273a352469b.JPG" alt="當 AI 成為開發主力" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="放手或許才是最高級的協作">放手，或許才是最高級的協作<a href="https://garden.liwen.studio/blog/ai-development-uncanny-valley#%E6%94%BE%E6%89%8B%E6%88%96%E8%A8%B1%E6%89%8D%E6%98%AF%E6%9C%80%E9%AB%98%E7%B4%9A%E7%9A%84%E5%8D%94%E4%BD%9C" class="hash-link" aria-label="放手，或許才是最高級的協作的直接連結" title="放手，或許才是最高級的協作的直接連結" translate="no">​</a></h3>
<p>過去我們習慣思考如何用特定的技術（技術選型）來實作功能，但現在如果我們強行用自己有限的技術認知去規範 AI 的開發路徑，結果往往不如直接告訴 AI 目標，讓他自己尋找最優解。</p>
<p>就像詠春拳教導的，「不要跟他拼拳，要切他中路」。與其在 AI 最擅長的「寫程式」與「邏輯運算」上硬碰硬，我們應該找到目前 AI 的「短板」，那才是人類工程師的核心戰場。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="工程師的切中路策略從執行者轉向設計師">工程師的「切中路」策略：從執行者轉向設計師<a href="https://garden.liwen.studio/blog/ai-development-uncanny-valley#%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E5%88%87%E4%B8%AD%E8%B7%AF%E7%AD%96%E7%95%A5%E5%BE%9E%E5%9F%B7%E8%A1%8C%E8%80%85%E8%BD%89%E5%90%91%E8%A8%AD%E8%A8%88%E5%B8%AB" class="hash-link" aria-label="工程師的「切中路」策略：從執行者轉向設計師的直接連結" title="工程師的「切中路」策略：從執行者轉向設計師的直接連結" translate="no">​</a></h3>
<p>既然 AI 擅長產出，那麼我們就專注在產出前的「需求收斂」與產出後的「價值整合」。我分析了目前 AI 尚無法完美取代的四個核心領域：</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-跨部門工作流workflow的梳理與整合">1. 跨部門工作流（Workflow）的梳理與整合<a href="https://garden.liwen.studio/blog/ai-development-uncanny-valley#1-%E8%B7%A8%E9%83%A8%E9%96%80%E5%B7%A5%E4%BD%9C%E6%B5%81workflow%E7%9A%84%E6%A2%B3%E7%90%86%E8%88%87%E6%95%B4%E5%90%88" class="hash-link" aria-label="1. 跨部門工作流（Workflow）的梳理與整合的直接連結" title="1. 跨部門工作流（Workflow）的梳理與整合的直接連結" translate="no">​</a></h4>
<p>AI 無法直接走進辦公室，理解各部門之間那些混亂、充滿人性與政治角力的溝通鏈。我們不可能直接把一台掛著 AI Agent 的電腦扔給業務部門，就指望他們梳理出自動化的邏輯。</p>
<ul>
<li class=""><strong>人類的價值：</strong> 我們扮演「翻譯者」與「架構師」，去洞察部門真實的痛點、梳理資源流程，並將這些充滿雜訊的訊息，轉化為 AI 可理解的精確規格。</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-專業需求分析與深度檢核">2. 專業需求分析與深度檢核<a href="https://garden.liwen.studio/blog/ai-development-uncanny-valley#2-%E5%B0%88%E6%A5%AD%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90%E8%88%87%E6%B7%B1%E5%BA%A6%E6%AA%A2%E6%A0%B8" class="hash-link" aria-label="2. 專業需求分析與深度檢核的直接連結" title="2. 專業需求分析與深度檢核的直接連結" translate="no">​</a></h4>
<p>使用者往往無法清楚描述自己要什麼，他們說的是「結果」，而非「需求」。</p>
<ul>
<li class=""><strong>人類的價值：</strong> 我們用專業知識幫使用者收斂需求，確保 AI 產出的產品符合預期；並在產出後，扮演「品質守門員」，檢核效能、資安、可維護性等面向。雖然 AI 未來可能也會考慮這些，但這正是我們現在發揮經驗積累的關鍵。</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-系統架構設計與技術債治理">3. 系統架構設計與「技術債」治理<a href="https://garden.liwen.studio/blog/ai-development-uncanny-valley#3-%E7%B3%BB%E7%B5%B1%E6%9E%B6%E6%A7%8B%E8%A8%AD%E8%A8%88%E8%88%87%E6%8A%80%E8%A1%93%E5%82%B5%E6%B2%BB%E7%90%86" class="hash-link" aria-label="3. 系統架構設計與「技術債」治理的直接連結" title="3. 系統架構設計與「技術債」治理的直接連結" translate="no">​</a></h4>
<p>AI 寫單一功能（Happy Path）非常快，但它往往缺乏對整個專案「宏觀架構」的想像力。如果完全放任 AI 寫程式，很容易產出看起來能動，但未來難以擴充的「義大利麵條程式碼」。</p>
<ul>
<li class=""><strong>人類的價值：</strong> 我們必須身兼「架構師」的角色，不只要確保模組化設計（如 MVC、Design Patterns），更要防堵 AI 產生幻覺或默默累積巨大的「技術債」，確保系統底層的健康與高擴充性。</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-邊界條件edge-cases與複雜除錯">4. 邊界條件（Edge Cases）與複雜除錯<a href="https://garden.liwen.studio/blog/ai-development-uncanny-valley#4-%E9%82%8A%E7%95%8C%E6%A2%9D%E4%BB%B6edge-cases%E8%88%87%E8%A4%87%E9%9B%9C%E9%99%A4%E9%8C%AF" class="hash-link" aria-label="4. 邊界條件（Edge Cases）與複雜除錯的直接連結" title="4. 邊界條件（Edge Cases）與複雜除錯的直接連結" translate="no">​</a></h4>
<p>當系統在完美狀態下運行，AI 無懈可擊；但真實世界總是充滿意外，例如第三方 API 文件寫錯、網路不穩的玄學問題、或是各種奇葩的使用者行為，這時 AI 往往會陷入鬼打牆的死胡同。</p>
<ul>
<li class=""><strong>人類的價值：</strong> 我們憑藉著多年的踩雷經驗與「除錯直覺」，在處理極端邊界條件、效能瓶頸或冷門的底層環境問題時，依然是目前無可取代的終極救火隊長。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="未來的技能發展方向">未來的技能發展方向<a href="https://garden.liwen.studio/blog/ai-development-uncanny-valley#%E6%9C%AA%E4%BE%86%E7%9A%84%E6%8A%80%E8%83%BD%E7%99%BC%E5%B1%95%E6%96%B9%E5%90%91" class="hash-link" aria-label="未來的技能發展方向的直接連結" title="未來的技能發展方向的直接連結" translate="no">​</a></h3>
<p>思考過後，我認為未來軟體工程師的職涯發展，應該朝向以下幾個維度進行自我優化：</p>
<ul>
<li class=""><strong>從「會用 AI」升級為「打造 AI 生態」：</strong> 當熟練操作 AI 工具已成為所有工程師的「基本盤」，未來的突圍方向將是「AI 產品化」。不再只是用 AI 寫扣，而是要具備設計 AI Agent 工作流、甚至是將企業內部痛點結合 LLM 轉化為自動化服務架構的能力。</li>
<li class=""><strong>深耕業務邏輯優化：</strong> 成為更懂「業務」的開發者。利用 AI 高效率產出程式碼，我們則騰出時間去優化整體部門的邏輯結構與自動化流程。</li>
<li class=""><strong>跨領域的技術整合（全端思維）：</strong> AI 大幅降低了語法的學習門檻，前端跨後端、甚至跨雲端部署的成本變得極低。未來不再死守單一技術棧，能運用 AI 串聯 Frontend、Backend 到 DevOps 的「全端整合者」將大放異彩。</li>
<li class=""><strong>軟實力與影響力升級：</strong> 當「寫出能動的程式」變成基本盤，工程師的護城河將轉移到跨部門溝通、專案時程控管，以及將技術語言轉化為商業價值的說服力。這些充滿「人味」的軟實力，是 AI 最難以複製的籌碼。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="結語">結語<a href="https://garden.liwen.studio/blog/ai-development-uncanny-valley#%E7%B5%90%E8%AA%9E" class="hash-link" aria-label="結語的直接連結" title="結語的直接連結" translate="no">​</a></h3>
<p>不要再糾結於「寫程式這件事我輸給了 AI」。當我們不再執著於拼拳，轉而專注於梳理邏輯與串連價值，你會發現，人類工程師並未被淘汰，我們只是從「磚塊搬運工」，變成了「系統建築師」。</p>]]></content:encoded>
            <category>AI協作</category>
            <category>職涯發展</category>
            <category>隨筆</category>
        </item>
        <item>
            <title><![CDATA[【開發者心聲】當「技術優化」變成「職場許願池」，我們該如何應對？]]></title>
            <link>https://garden.liwen.studio/blog/tech-optimization-vs-wishlist</link>
            <guid>https://garden.liwen.studio/blog/tech-optimization-vs-wishlist</guid>
            <pubDate>Wed, 13 May 2026 01:43:00 GMT</pubDate>
            <description><![CDATA[探討職場中「技術優化」為何常淪為缺乏業務邏輯的許願池，並分享工程師如何建立協作防線，拒絕無效通靈開發。]]></description>
            <content:encoded><![CDATA[<p>身為工程師，我們最怕的不是複雜的演算法，而是那種「邏輯斷層」的溝通。最近在思考職場定位時，有感而發想聊聊一個現象：為什麼有些優化專案，最後會變成技術人員的災難？</p>
<p>當「技術優化」的口號響起，如果缺乏清晰的業務定義，工程師往往會從「解決問題的人」變成「幫人通靈的人」。這不僅是效率的浪費，更是對專業價值的消耗。</p>
<!-- -->
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="為什麼優化會變成災難">為什麼優化會變成災難？<a href="https://garden.liwen.studio/blog/tech-optimization-vs-wishlist#%E7%82%BA%E4%BB%80%E9%BA%BC%E5%84%AA%E5%8C%96%E6%9C%83%E8%AE%8A%E6%88%90%E7%81%BD%E9%9B%A3" class="hash-link" aria-label="為什麼優化會變成災難？的直接連結" title="為什麼優化會變成災難？的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-消失的需求發起人傳聲筒式溝通">1. 消失的需求發起人：傳聲筒式溝通<a href="https://garden.liwen.studio/blog/tech-optimization-vs-wishlist#1-%E6%B6%88%E5%A4%B1%E7%9A%84%E9%9C%80%E6%B1%82%E7%99%BC%E8%B5%B7%E4%BA%BA%E5%82%B3%E8%81%B2%E7%AD%92%E5%BC%8F%E6%BA%9D%E9%80%9A" class="hash-link" aria-label="1. 消失的需求發起人：傳聲筒式溝通的直接連結" title="1. 消失的需求發起人：傳聲筒式溝通的直接連結" translate="no">​</a></h3>
<p>在理想的開發流程中，需求應該來自對業務有深刻理解的使用端。但現實中，常出現一種「傳聲筒式」的需求：上層給了壓力，中間單位便列出一長串清單，卻沒有人能解釋清單背後的業務邏輯。</p>
<p>當技術端被要求「主動追蹤」這些模糊的需求時，我們其實是在替別人思考。這種「思考轉嫁」不僅效率極低，更讓工程師變成了行政追蹤員。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-專業邏輯的界線技術不是萬能丹">2. 專業邏輯的界線：技術不是萬能丹<a href="https://garden.liwen.studio/blog/tech-optimization-vs-wishlist#2-%E5%B0%88%E6%A5%AD%E9%82%8F%E8%BC%AF%E7%9A%84%E7%95%8C%E7%B7%9A%E6%8A%80%E8%A1%93%E4%B8%8D%E6%98%AF%E8%90%AC%E8%83%BD%E4%B8%B9" class="hash-link" aria-label="2. 專業邏輯的界線：技術不是萬能丹的直接連結" title="2. 專業邏輯的界線：技術不是萬能丹的直接連結" translate="no">​</a></h3>
<p>最令人無奈的情境是：當系統計算結果與預期不符時，使用者回了一句：「我不知道，那是系統（或 AI）算的。」</p>
<p>這反映了一個嚴重的認知偏誤：<strong>技術可以優化流程，但無法「發明」業務邏輯。</strong>
如果連使用單位都說不清楚核心計算公式，卻期待工程師能通靈產出答案，這不叫優化，這叫「盲目建設」。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-被瑣事消磨的技術熱忱">3. 被瑣事消磨的技術熱忱<a href="https://garden.liwen.studio/blog/tech-optimization-vs-wishlist#3-%E8%A2%AB%E7%91%A3%E4%BA%8B%E6%B6%88%E7%A3%A8%E7%9A%84%E6%8A%80%E8%A1%93%E7%86%B1%E5%BF%B1" class="hash-link" aria-label="3. 被瑣事消磨的技術熱忱的直接連結" title="3. 被瑣事消磨的技術熱忱的直接連結" translate="no">​</a></h3>
<p>開發者的成就感來自於解決具挑戰性的技術問題，或是學習新的技術架構。然而，當大部分的時間被浪費在「釐清不存在的邏輯」或「處理白做工的行政流程」時，技術熱忱就會迅速被消磨。</p>
<blockquote>
<p><strong>「白做工」是職場上最大的資源浪費。</strong></p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="建立健康的協作防線開發者的自我守護">建立健康的協作防線：開發者的自我守護<a href="https://garden.liwen.studio/blog/tech-optimization-vs-wishlist#%E5%BB%BA%E7%AB%8B%E5%81%A5%E5%BA%B7%E7%9A%84%E5%8D%94%E4%BD%9C%E9%98%B2%E7%B7%9A%E9%96%8B%E7%99%BC%E8%80%85%E7%9A%84%E8%87%AA%E6%88%91%E5%AE%88%E8%AD%B7" class="hash-link" aria-label="建立健康的協作防線：開發者的自我守護的直接連結" title="建立健康的協作防線：開發者的自我守護的直接連結" translate="no">​</a></h2>
<p>為了避免成為無底洞的許願池，我認為技術端需要建立幾道防線：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="️-拒絕通靈開發">🛡️ 拒絕「通靈」開發<a href="https://garden.liwen.studio/blog/tech-optimization-vs-wishlist#%EF%B8%8F-%E6%8B%92%E7%B5%95%E9%80%9A%E9%9D%88%E9%96%8B%E7%99%BC" class="hash-link" aria-label="🛡️ 拒絕「通靈」開發的直接連結" title="🛡️ 拒絕「通靈」開發的直接連結" translate="no">​</a></h3>
<p>要求需求方必須提供明確的業務邏輯與預期目標。如果對方說不清楚「為什麼要做」與「規則是什麼」，那麼這個開發任務就不應該啟動。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-釐清責任邊界">🤝 釐清責任邊界<a href="https://garden.liwen.studio/blog/tech-optimization-vs-wishlist#-%E9%87%90%E6%B8%85%E8%B2%AC%E4%BB%BB%E9%82%8A%E7%95%8C" class="hash-link" aria-label="🤝 釐清責任邊界的直接連結" title="🤝 釐清責任邊界的直接連結" translate="no">​</a></h3>
<p>技術負責「實現（How）」，業務負責「定義（What）」。當邊界模糊時，工程師會被迫承擔非專業領域的決策壓力，這對雙方都是風險。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-守護核心時間">⏳ 守護核心時間<a href="https://garden.liwen.studio/blog/tech-optimization-vs-wishlist#-%E5%AE%88%E8%AD%B7%E6%A0%B8%E5%BF%83%E6%99%82%E9%96%93" class="hash-link" aria-label="⏳ 守護核心時間的直接連結" title="⏳ 守護核心時間的直接連結" translate="no">​</a></h3>
<p>減少無效的行政追蹤。工程師的時間應該花在優化架構、提升效能，或是探索能帶來真實價值的技術迭代，而不是在混亂的溝通中空轉。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="結語把時間花在創造價值">結語：把時間花在創造價值<a href="https://garden.liwen.studio/blog/tech-optimization-vs-wishlist#%E7%B5%90%E8%AA%9E%E6%8A%8A%E6%99%82%E9%96%93%E8%8A%B1%E5%9C%A8%E5%89%B5%E9%80%A0%E5%83%B9%E5%80%BC" class="hash-link" aria-label="結語：把時間花在創造價值的直接連結" title="結語：把時間花在創造價值的直接連結" translate="no">​</a></h2>
<p>與其在混亂的流程中掙扎，不如重新思考如何優化協作機制。我們應該把時間花在創造真實的價值，而不是在別人的懶惰或邏輯斷層中打轉。</p>
<p><strong>優化的第一步，往往不是改 Code，而是先優化溝通。</strong></p>]]></content:encoded>
            <category>職場</category>
            <category>工程文化</category>
            <category>心法</category>
        </item>
        <item>
            <title><![CDATA[ai coding 讓我開始害怕自己忘記怎麼思考]]></title>
            <link>https://garden.liwen.studio/blog/ai-coding-reflection</link>
            <guid>https://garden.liwen.studio/blog/ai-coding-reflection</guid>
            <pubDate>Wed, 06 May 2026 00:06:32 GMT</pubDate>
            <description><![CDATA[深度反思 AI 輔助寫程式對開發者邏輯能力的影響，探討如何從『結果導向』轉向『邏輯導向』的 AI 協作模式。]]></description>
            <content:encoded><![CDATA[<p>最近我有個習慣讓我有點不安——我發現我在用 AI 寫程式的時候，根本不會先想「這要怎麼做」，而是直接打開對話框，把需求貼進去，等結果出來。</p>
<p>有一天我突然意識到：如果 AI 今天消失了，我還會寫程式嗎？</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一個讓我驚醒的小需求">一個讓我驚醒的小需求<a href="https://garden.liwen.studio/blog/ai-coding-reflection#%E4%B8%80%E5%80%8B%E8%AE%93%E6%88%91%E9%A9%9A%E9%86%92%E7%9A%84%E5%B0%8F%E9%9C%80%E6%B1%82" class="hash-link" aria-label="一個讓我驚醒的小需求的直接連結" title="一個讓我驚醒的小需求的直接連結" translate="no">​</a></h2>
<p>事情是這樣的。我在做一個功能，有個欄位裡面存了多家公司的 tag，產品需求是：<strong>超過 3 家公司的時候，不要全部列出來，改成顯示一個「Open Model」的 tag，使用者點擊之後跳出彈跳視窗看明細。</strong></p>
<p>以前的我，會直接這樣跟 AI 說：</p>
<blockquote>
<p>「這欄位裡面抓出來的公司，如果超過 3 家直接幫我顯示 open model 的 tag，點開之後我要可以看到明細」</p>
</blockquote>
<p>這樣說沒有錯，功能也會動。</p>
<p>但我發現一件事：<strong>我完全不知道 AI 怎麼做到的。</strong></p>
<p>我不知道它用什麼資料結構存公司清單，不知道它怎麼判斷數量，不知道 modal 是怎麼觸發的。如果它生出來的東西有 bug，我甚至不知道要從哪裡下手。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我決定改變問法">我決定改變問法<a href="https://garden.liwen.studio/blog/ai-coding-reflection#%E6%88%91%E6%B1%BA%E5%AE%9A%E6%94%B9%E8%AE%8A%E5%95%8F%E6%B3%95" class="hash-link" aria-label="我決定改變問法的直接連結" title="我決定改變問法的直接連結" translate="no">​</a></h2>
<p>後來我強迫自己先想一遍流程，再跟 AI 說：</p>
<blockquote>
<p>「這個欄位抓出來的公司清單，幫我用陣列變數先儲存起來。如果公司數量超過 3 家，顯示 Open Model 的 tag，使用者點擊之後跳出彈跳視窗，裡面列出完整的公司明細。」</p>
</blockquote>
<p>差在哪裡？</p>
<p>我在說這句話之前，腦袋裡已經跑過一遍：</p>
<ul>
<li class="">先用<strong>陣列</strong>把資料存起來</li>
<li class=""><strong>判斷陣列長度</strong>是否超過 3</li>
<li class="">超過的話<strong>渲染 tag</strong></li>
<li class="">點擊觸發<strong>modal 顯示明細</strong></li>
</ul>
<p>AI 只是幫我把這個邏輯寫成程式碼，<strong>邏輯是我的</strong>。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="用程式碼來說明差異">用程式碼來說明差異<a href="https://garden.liwen.studio/blog/ai-coding-reflection#%E7%94%A8%E7%A8%8B%E5%BC%8F%E7%A2%BC%E4%BE%86%E8%AA%AA%E6%98%8E%E5%B7%AE%E7%95%B0" class="hash-link" aria-label="用程式碼來說明差異的直接連結" title="用程式碼來說明差異的直接連結" translate="no">​</a></h2>
<p>讓我用實際的例子讓這個差異更具體。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="結果導向的問法ai-可能生出這種東西">「結果導向」的問法，AI 可能生出這種東西：<a href="https://garden.liwen.studio/blog/ai-coding-reflection#%E7%B5%90%E6%9E%9C%E5%B0%8E%E5%90%91%E7%9A%84%E5%95%8F%E6%B3%95ai-%E5%8F%AF%E8%83%BD%E7%94%9F%E5%87%BA%E9%80%99%E7%A8%AE%E6%9D%B1%E8%A5%BF" class="hash-link" aria-label="「結果導向」的問法，AI 可能生出這種東西：的直接連結" title="「結果導向」的問法，AI 可能生出這種東西：的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">CompanyTags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token parameter"> companies </span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">companies</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">length</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">span</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">className</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">tag</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">Open Model</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">span</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> companies</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">c</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">span</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">className</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">tag</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">span</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>功能是有了，但你看到這段 code 可能還是霧煞煞——modal 呢？明細呢？你可能要再問一次，然後 AI 幫你加上去，結果整個邏輯散在各地，你也不知道為什麼。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="邏輯導向的問法你腦袋裡有架構ai-生出來的東西才是完整的">「邏輯導向」的問法，你腦袋裡有架構，AI 生出來的東西才是完整的：<a href="https://garden.liwen.studio/blog/ai-coding-reflection#%E9%82%8F%E8%BC%AF%E5%B0%8E%E5%90%91%E7%9A%84%E5%95%8F%E6%B3%95%E4%BD%A0%E8%85%A6%E8%A2%8B%E8%A3%A1%E6%9C%89%E6%9E%B6%E6%A7%8Bai-%E7%94%9F%E5%87%BA%E4%BE%86%E7%9A%84%E6%9D%B1%E8%A5%BF%E6%89%8D%E6%98%AF%E5%AE%8C%E6%95%B4%E7%9A%84" class="hash-link" aria-label="「邏輯導向」的問法，你腦袋裡有架構，AI 生出來的東西才是完整的：的直接連結" title="「邏輯導向」的問法，你腦袋裡有架構，AI 生出來的東西才是完整的：的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> useState </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"react"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">CompanyTags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token parameter"> companies </span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">isOpen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> setIsOpen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 1. 用陣列變數儲存公司清單（companies 本身就是陣列）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 2. 判斷數量是否超過 3 家</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> isOverLimit </span><span class="token operator">=</span><span class="token plain"> companies</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">length</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">isOverLimit </span><span class="token operator">?</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// 3. 超過就顯示 Open Model tag</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">span</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">          </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">className</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">tag open-model</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">          </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript arrow operator" style="color:rgb(255, 121, 198)">=&gt;</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript function" style="color:rgb(80, 250, 123)">setIsOpen</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript boolean" style="color:rgb(255, 121, 198)">true</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">          </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript literal-property property" style="color:rgb(255, 121, 198)">cursor</span><span class="token tag script language-javascript operator" style="color:rgb(255, 121, 198)">:</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript string" style="color:rgb(255, 121, 198)">"pointer"</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">          Open Model</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">span</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// 沒超過就正常列出</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        companies</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">company</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">span</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">i</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">className</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">tag</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">company</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">span</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token comment" style="color:rgb(98, 114, 164)">/* 4. Modal：顯示公司明細 */</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">isOpen </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">className</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">modal-overlay</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript arrow operator" style="color:rgb(255, 121, 198)">=&gt;</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript function" style="color:rgb(80, 250, 123)">setIsOpen</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript boolean" style="color:rgb(255, 121, 198)">false</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">className</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">modal-box</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript parameter" style="color:rgb(255, 121, 198)">e</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript arrow operator" style="color:rgb(255, 121, 198)">=&gt;</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> e</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token tag script language-javascript method function property-access" style="color:rgb(80, 250, 123)">stopPropagation</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">h3</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">公司清單</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">h3</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">ul</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">              </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">companies</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">company</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">li</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">i</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">company</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">li</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">ul</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">button</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript arrow operator" style="color:rgb(255, 121, 198)">=&gt;</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript function" style="color:rgb(80, 250, 123)">setIsOpen</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript boolean" style="color:rgb(255, 121, 198)">false</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">關閉</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">button</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>這版你看得懂：陣列在哪、判斷在哪、modal 怎麼開、怎麼關。哪裡出問題，你知道要去找哪裡。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="兩種問法的本質差異">兩種問法的本質差異<a href="https://garden.liwen.studio/blog/ai-coding-reflection#%E5%85%A9%E7%A8%AE%E5%95%8F%E6%B3%95%E7%9A%84%E6%9C%AC%E8%B3%AA%E5%B7%AE%E7%95%B0" class="hash-link" aria-label="兩種問法的本質差異的直接連結" title="兩種問法的本質差異的直接連結" translate="no">​</a></h2>
<table><thead><tr><th></th><th>結果導向</th><th>邏輯導向</th></tr></thead><tbody><tr><td>你在做什麼</td><td>描述你要什麼</td><td>描述怎麼做到</td></tr><tr><td>AI 的角色</td><td>幫你解決問題</td><td>幫你把想法寫成 code</td></tr><tr><td>出 bug 時</td><td>不知從何下手</td><td>知道要去哪裡找</td></tr><tr><td>長期影響</td><td>邏輯能力退化</td><td>持續在練習拆解問題</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="其實這不是-ai-的問題">其實這不是 AI 的問題<a href="https://garden.liwen.studio/blog/ai-coding-reflection#%E5%85%B6%E5%AF%A6%E9%80%99%E4%B8%8D%E6%98%AF-ai-%E7%9A%84%E5%95%8F%E9%A1%8C" class="hash-link" aria-label="其實這不是 AI 的問題的直接連結" title="其實這不是 AI 的問題的直接連結" translate="no">​</a></h2>
<p>仔細想想，這個問題不是 AI 造成的，而是<strong>我讓自己跳過了思考的步驟</strong>。</p>
<p>以前沒有 AI 的時候，我要自己 Google、自己拼湊、自己想架構——那個過程雖然痛苦，但正是那個過程在鍛鍊邏輯。</p>
<p>AI 的出現，讓這個過程可以跳過。但跳過不代表不需要。</p>
<p>所以我現在給自己定了一個規則：</p>
<blockquote>
<p><strong>在打開 AI 之前，先在腦袋裡或紙上把步驟拆一遍。</strong></p>
</blockquote>
<p>不需要想到 code 層級，但至少要想到：資料怎麼存、判斷條件是什麼、觸發什麼事件、呈現什麼結果。</p>
<p>這樣 AI 就回到它該有的位置——<strong>是工具，不是大腦。</strong></p>
<hr>]]></content:encoded>
            <category>ai</category>
            <category>職涯心理</category>
        </item>
        <item>
            <title><![CDATA[小夕出月中記：新手爸爸的三棲戰士日常與育兒觀念碰撞]]></title>
            <link>https://garden.liwen.studio/blog/weekend-dad-full-time-heart</link>
            <guid>https://garden.liwen.studio/blog/weekend-dad-full-time-heart</guid>
            <pubDate>Sun, 03 May 2026 11:37:41 GMT</pubDate>
            <description><![CDATA[記錄新手爸爸在公司、月子中心與家庭間奔波的『三棲戰士』生活，並探討長輩傳統育兒觀點與現代護理觀念間的摩擦與反思。]]></description>
            <content:encoded><![CDATA[<p>小夕終於出月中了！回顧這一個月，日子過得既疲憊又充實，還充滿了各種「驚喜」。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三棲戰士的日常">三棲戰士的日常<a href="https://garden.liwen.studio/blog/weekend-dad-full-time-heart#%E4%B8%89%E6%A3%B2%E6%88%B0%E5%A3%AB%E7%9A%84%E6%97%A5%E5%B8%B8" class="hash-link" aria-label="三棲戰士的日常的直接連結" title="三棲戰士的日常的直接連結" translate="no">​</a></h2>
<p>這段時間，我成了名副其實的「三棲戰士」：<br>
<!-- -->每天在公司、月中、家裡三個點之間來回奔波，除了要應付公司裡的煩心事，還要抓緊時間學習育兒技能。</p>
<!-- -->
<p>雖然累到快被疲勞轟炸，但收穫也很多——從怎麼餵奶、洗澡，到解讀寶寶的需求，這些都是以前難以想像的挑戰。</p>
<p>以前總覺得玩別人的小孩很簡單，自己動手才發現：要照顧好一個生命真的好難。看著小夕喝完奶扭來扭去不睡覺，到底是要換尿布還是沒吃飽？每一組肢體動作都是密碼，雖然燒腦，但也很有趣。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="出月中後的挑戰">出月中後的挑戰<a href="https://garden.liwen.studio/blog/weekend-dad-full-time-heart#%E5%87%BA%E6%9C%88%E4%B8%AD%E5%BE%8C%E7%9A%84%E6%8C%91%E6%88%B0" class="hash-link" aria-label="出月中後的挑戰的直接連結" title="出月中後的挑戰的直接連結" translate="no">​</a></h2>
<p>然而，挑戰才剛開始，出月中後我們即將面臨暫時的分隔。<br>
<!-- -->因為規劃的關係，小夕要回台北長住，我也正式進入「假日爸爸」的模式。想到這裡難免有些感傷，但在這轉換的過程中，我更深刻體會到：<strong>育兒觀念的碰撞，才是最考驗人的修行。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="與岳母的育兒觀念差異">與岳母的育兒觀念差異<a href="https://garden.liwen.studio/blog/weekend-dad-full-time-heart#%E8%88%87%E5%B2%B3%E6%AF%8D%E7%9A%84%E8%82%B2%E5%85%92%E8%A7%80%E5%BF%B5%E5%B7%AE%E7%95%B0" class="hash-link" aria-label="與岳母的育兒觀念差異的直接連結" title="與岳母的育兒觀念差異的直接連結" translate="no">​</a></h2>
<p>我岳母是一位專業保母，領有證照，最近剛帶完一對雙胞胎，本來打算進入退休生活，剛好小夕「報到」，讓她的退休生活一點都不無聊。</p>
<p>但也因為長輩有著深厚的「實戰經驗」，讓我們在月中學到的現代護理觀念，面臨了前所未有的挑戰：</p>
<ol>
<li class="">
<p><strong>痱子粉才是萬能，屁屁膏都是化學？</strong><br>
<!-- -->關於屁屁護理，岳母堅持使用她那「加了中藥、比較天然」的痱子粉，直接拒絕了我們準備的屁屁膏。</p>
</li>
<li class="">
<p><strong>母奶至上，配方奶是外道？</strong><br>
<!-- -->在月中時，因為小夕便便次數過多，醫生建議先換無乳糖配方奶觀察。沒想到一回台北，岳母立刻下令「全面改回母奶」，甚至認為寶寶拉肚子是因為媽媽亂吃（完全無視月中營養師配過的月子餐，她一直覺得月子餐沒她煮的補）。</p>
</li>
<li class="">
<p><strong>乳液有害論：洗完澡擦乳液？我都沒在用的</strong><br>
<!-- -->建議洗澡後幫寶寶塗個乳液，換來的是一句：「我帶過這麼多小孩都沒在擦，那都是化學的東西，對小孩不好。」聽得我只能乾笑。</p>
</li>
<li class="">
<p><strong>肥皂絕對優於沐浴乳</strong><br>
<!-- -->岳母拿出一塊肥皂要我帶回家，強調她都用這個洗，還嫌棄我們買的沐浴產品。<br>
<!-- -->我提醒她：「肥皂裡有香精，小夕現在不適合碰。」她卻回我：「但這是嬰兒專用的啊！」<br>
<!-- -->我只好無奈反擊：「我的乳液也是嬰兒用的啊...」最後只能在尷尬中不了了之。</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="心情與期待">心情與期待<a href="https://garden.liwen.studio/blog/weekend-dad-full-time-heart#%E5%BF%83%E6%83%85%E8%88%87%E6%9C%9F%E5%BE%85" class="hash-link" aria-label="心情與期待的直接連結" title="心情與期待的直接連結" translate="no">​</a></h2>
<p>老實說，心裡真的很不爽，但礙於對方是長輩又是幫忙帶小孩，真的難以言說。<br>
<!-- -->我打算先冷靜觀察一週，看看寶寶適應的情況。</p>
<hr>
<p><strong>小夕，你要撐住啊！爸爸下禮拜就去接你。</strong></p>]]></content:encoded>
            <category>育兒</category>
            <category>生活紀錄</category>
            <category>心理成長</category>
        </item>
        <item>
            <title><![CDATA[Vibe Coding 很爽，但別讓 AI 隱藏的「安全地雷」毀掉你的產品]]></title>
            <link>https://garden.liwen.studio/blog/ai-vibe-coding</link>
            <guid>https://garden.liwen.studio/blog/ai-vibe-coding</guid>
            <pubDate>Thu, 23 Apr 2026 05:10:26 GMT</pubDate>
            <description><![CDATA[探討 AI 輔助開發（Vibe Coding）帶來的安全風險，包含 API Key 洩漏、SQL 注入與部署安全等實戰保命指南。]]></description>
            <content:encoded><![CDATA[
<p>最近開發圈最迷人的詞莫過於 <strong>Vibe Coding</strong>——打幾句話，看著 AI 幫你把功能生出來，測試沒問題就光速部署。以前要花好幾天的專案，現在一個下午就能搞定。</p>
<p>說真的，這種「腦力即產力」的感覺非常爽。但就在這種高速生產的流程中，我注意到了一個隱形危機：<strong>流程太順了，順到讓我們忘記了身為開發者最基本的「防禦本能」。</strong></p>
<p><code>{</code>/* truncate */<code>}</code></p>
<p>如果你也熱衷於 Vibe Coding，這篇「保命指南」你一定要加入收藏。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-第一關你的-api-key正在-github-上裸奔嗎">🛑 第一關：你的 API Key，正在 GitHub 上裸奔嗎？<a href="https://garden.liwen.studio/blog/ai-vibe-coding#-%E7%AC%AC%E4%B8%80%E9%97%9C%E4%BD%A0%E7%9A%84-api-key%E6%AD%A3%E5%9C%A8-github-%E4%B8%8A%E8%A3%B8%E5%A5%94%E5%97%8E" class="hash-link" aria-label="🛑 第一關：你的 API Key，正在 GitHub 上裸奔嗎？的直接連結" title="🛑 第一關：你的 API Key，正在 GitHub 上裸奔嗎？的直接連結" translate="no">​</a></h2>
<p>這是最常見、也最容易導致「信用卡刷爆」的失誤。</p>
<p>當你叫 AI 快速串接 OpenAI 或 Stripe API 時，它生成的範例程式碼為了讓你直接跑通，往往會長這樣：</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">/* ⚠️ 這是自殺式寫法：千萬別學 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> openai </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">OpenAI</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">apiKey</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"sk-proj-xxxxxxxxxxxxxxxxxxxxxxxx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// AI 為了方便，直接幫你填在這裡了</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>如果你直接 <code>git push</code> 上 GitHub，不到一分鐘，全世界的爬蟲機器人都會拿到你的金鑰，開始瘋狂盜刷。</p>
<blockquote>
<p>[!WARNING]
<strong>緊急救援：</strong> 如果金鑰已經 push 出去，請第一時間去後台 <strong>廢掉 (Rotate) 那支 Key</strong>！「刪掉 commit」是救不了你的，因為 Git 的歷史紀錄會永遠留存。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-正確的優雅姿勢">✅ 正確的優雅姿勢：<a href="https://garden.liwen.studio/blog/ai-vibe-coding#-%E6%AD%A3%E7%A2%BA%E7%9A%84%E5%84%AA%E9%9B%85%E5%A7%BF%E5%8B%A2" class="hash-link" aria-label="✅ 正確的優雅姿勢：的直接連結" title="✅ 正確的優雅姿勢：的直接連結" translate="no">​</a></h3>
<p>請把金鑰放進 <code>.env</code> 檔案，並嚴格檢查你的 <code>.gitignore</code>。</p>
<p>如果你的專案規模較大，強烈建議改用 <strong><a href="https://infisical.com/" target="_blank" rel="noopener noreferrer" class="">Infisical</a></strong> 或 <strong><a href="https://aws.amazon.com/secrets-manager/" target="_blank" rel="noopener noreferrer" class="">AWS Secrets Manager</a></strong> 這種專門的加密管理平台，這能讓你即便不小心洩漏了環境變數檔案，金鑰依然安全無虞。</p>
<blockquote>
<p>[!TIP]
<strong>Next.js 警語：</strong> 千萬別在私密金鑰前綴加上 <code>NEXT_PUBLIC_</code>，否則它會被打包進前端。到時候讀者只要按一下 F12，你的私鑰就會直接在瀏覽器裡跟你招手。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="️-第二關ai-只管功能它不會主動擋壞人">🛡️ 第二關：AI 只管功能，它不會主動「擋壞人」<a href="https://garden.liwen.studio/blog/ai-vibe-coding#%EF%B8%8F-%E7%AC%AC%E4%BA%8C%E9%97%9Cai-%E5%8F%AA%E7%AE%A1%E5%8A%9F%E8%83%BD%E5%AE%83%E4%B8%8D%E6%9C%83%E4%B8%BB%E5%8B%95%E6%93%8B%E5%A3%9E%E4%BA%BA" class="hash-link" aria-label="🛡️ 第二關：AI 只管功能，它不會主動「擋壞人」的直接連結" title="🛡️ 第二關：AI 只管功能，它不會主動「擋壞人」的直接連結" translate="no">​</a></h2>
<p>AI 的天性是「解決問題」，它不太會主動考慮到「被惡搞的場景」。例如一個簡單的資料庫查詢，AI 可能會給你這樣的 code：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 經典的 SQL 注入溫床</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"INSERT INTO notes VALUES ('</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">user_input_text</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">')"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>這在資安領域叫做 <strong>SQL Injection</strong>。只要有人輸入一句特殊的代碼，你的整個資料庫內容可能就會被洗劫一空。</p>
<p><strong>Vibe Coder 必須建立的習慣：</strong></p>
<ul>
<li class=""><strong>不信任任何輸入</strong>：永遠檢查使用者傳過來的文字。</li>
<li class=""><strong>限制頻率 (Rate Limiting)</strong>：如果有人寫個迴圈狂灌你的 API，你的帳單會比你的程式碼跑得快。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-第三關部署後的隱形後門">🔍 第三關：部署後的「隱形後門」<a href="https://garden.liwen.studio/blog/ai-vibe-coding#-%E7%AC%AC%E4%B8%89%E9%97%9C%E9%83%A8%E7%BD%B2%E5%BE%8C%E7%9A%84%E9%9A%B1%E5%BD%A2%E5%BE%8C%E9%96%80" class="hash-link" aria-label="🔍 第三關：部署後的「隱形後門」的直接連結" title="🔍 第三關：部署後的「隱形後門」的直接連結" translate="no">​</a></h2>
<p>現代部署平台（如 Vercel, Railway）讓上線變得太簡單。但簡單的背後，這兩個地方最容易踩雷：</p>
<ol>
<li class=""><strong>Debug 模式忘了關</strong>：開著 Debug 模式上線，就像是把家裡的平面圖與保險箱密碼貼在大門口。它會把檔案路徑、環境變數詳細地報給使用者（和駭客）看。</li>
<li class=""><strong>資料庫公開存取</strong>：Supabase 或 Firebase 預設為了方便開發，有時會開放權限。<strong>上線前請務必檢查 Row Level Security (RLS)</strong>，確保那是鎖好的。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-vibe-coder-的安全百寶箱">🧰 Vibe Coder 的安全百寶箱<a href="https://garden.liwen.studio/blog/ai-vibe-coding#-vibe-coder-%E7%9A%84%E5%AE%89%E5%85%A8%E7%99%BE%E5%AF%B6%E7%AE%B1" class="hash-link" aria-label="🧰 Vibe Coder 的安全百寶箱的直接連結" title="🧰 Vibe Coder 的安全百寶箱的直接連結" translate="no">​</a></h2>
<p>為了讓你安心地「Vibe」，建議在工作流中加入這幾位「自動化保鏢」：</p>
<ul>
<li class=""><strong><a href="https://github.com/trufflesecurity/trufflehog" target="_blank" rel="noopener noreferrer" class="">Trufflehog (金鑰獵人)</a></strong>：專門掃描你的 Git 歷史，翻出那些被你遺忘在舊版本裡的秘密。</li>
<li class=""><strong><a href="https://snyk.io/" target="_blank" rel="noopener noreferrer" class="">Snyk (套件漏洞雷達)</a></strong> / <strong><a href="https://github.com/dependabot" target="_blank" rel="noopener noreferrer" class="">Dependabot</a></strong>：GitHub 內建或外掛。它們會監控你的套件庫，發現漏洞時會直接發一個 PR 幫你修好。</li>
<li class=""><strong><a href="https://github.com/gitleaks/gitleaks" target="_blank" rel="noopener noreferrer" class="">Gitleaks</a></strong>：這是最後一道防線，如果你想在本地端攔截失誤，它可以在你 <code>commit</code> 的那一刻阻止你把 Key 傳出去。</li>
<li class=""><strong><a href="https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning" target="_blank" rel="noopener noreferrer" class="">GitHub Secret Scanning</a></strong>：GitHub 官方內建的強大功能，能第一時間阻斷洩漏。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-總結快雖好但安全不能省">💡 總結：快雖好，但安全不能省<a href="https://garden.liwen.studio/blog/ai-vibe-coding#-%E7%B8%BD%E7%B5%90%E5%BF%AB%E9%9B%96%E5%A5%BD%E4%BD%86%E5%AE%89%E5%85%A8%E4%B8%8D%E8%83%BD%E7%9C%81" class="hash-link" aria-label="💡 總結：快雖好，但安全不能省的直接連結" title="💡 總結：快雖好，但安全不能省的直接連結" translate="no">​</a></h2>
<p>Vibe Coding 讓每個人都能成為「數位創作者」，這是一場民主化革命。但這也代表，以前工程師才會碰到的炸彈，現在我們每個人都要懂得拆。</p>
<p><strong>記住這個「安全清單」：</strong></p>
<ol>
<li class=""><strong>金鑰必進 <code>.env</code></strong>，且必須加入 <code>.gitignore</code>。</li>
<li class=""><strong>不信任輸入</strong>，一定要做內容驗證。</li>
<li class=""><strong>確認權限</strong>，上線前關閉 Debug 與資料庫公開權限。</li>
<li class=""><strong>定期檢核</strong>，跑一下 <code>npm audit</code> 看看套件健不健康。</li>
</ol>
<p><em>做產品本來就有風險，但這些低級錯誤不值得你冒險。</em></p>
<hr>]]></content:encoded>
            <category>AI</category>
            <category>Vibe Coding</category>
            <category>Web Security</category>
            <category>API Security</category>
        </item>
        <item>
            <title><![CDATA[雲端邊緣的折翼：Next.js 部署到 Cloudflare Pages 的 48 小時奮鬥實錄]]></title>
            <link>https://garden.liwen.studio/blog/nextjs-deployment-cloudflare-pages-struggle</link>
            <guid>https://garden.liwen.studio/blog/nextjs-deployment-cloudflare-pages-struggle</guid>
            <pubDate>Thu, 23 Apr 2026 00:56:25 GMT</pubDate>
            <description><![CDATA[記錄將 Next.js 應用從 Vercel 遷移至 Cloudflare Pages 的挑戰，深入分析 Edge Runtime 的 3MB 腳本限制、SDK 體積優化與 Node.js API 相容性問題。]]></description>
            <content:encoded><![CDATA[<p>在 Web 開發的世界裡，我們通常會從 Vercel 開始。它很美、很快、很有質感，就像是一個精心裝潢的豪華公寓。但隨著專案成長，我們開始在想：有沒有那種既能擁有極致性能，成本又更平易近人的「自由之地」？</p>
<p>於是，我們把目光投向了 Cloudflare。這兩天，我們就像是一群試圖帶著行李從 Vercel 搬家到 Cloudflare 邊緣計算之城的旅人。雖然最終我們因為行李太重而暫時撤退，但這場「失敗」留下的經驗教訓，或許比成功搬家更值得記錄。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="為什麼想告別-vercel擁抱-cloudflare">為什麼想告別 Vercel，擁抱 Cloudflare？<a href="https://garden.liwen.studio/blog/nextjs-deployment-cloudflare-pages-struggle#%E7%82%BA%E4%BB%80%E9%BA%BC%E6%83%B3%E5%91%8A%E5%88%A5-vercel%E6%93%81%E6%8A%B1-cloudflare" class="hash-link" aria-label="為什麼想告別 Vercel，擁抱 Cloudflare？的直接連結" title="為什麼想告別 Vercel，擁抱 Cloudflare？的直接連結" translate="no">​</a></h2>
<p>LiwenBlog CMS 雖然目前在 Vercel 運行良好，但我們心中一直有個「邊緣夢」：</p>
<ol>
<li class=""><strong>打破流量焦慮</strong>：Vercel 的商業方案雖然強大，但對於個人或小型工作室來說，Cloudflare Pages 那近乎無限的請求數和更透明的成本結構更具吸引力。</li>
<li class=""><strong>追求「真正的」邊緣</strong>：我們希望在離使用者最近的地方（Edge Runtime）處理資料，讓部落格的每一毫秒都能被節省下來。</li>
<li class=""><strong>技術棧的終極整合</strong>：既然我們的數據庫在 Supabase，如果能把 Next.js 也放在 Cloudflare 上，就能構建出一個完全去中心化、高性能的世界級技術棧。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="撞上那一堵牆數字背後的物理極限">撞上那一堵牆：數字背後的物理極限<a href="https://garden.liwen.studio/blog/nextjs-deployment-cloudflare-pages-struggle#%E6%92%9E%E4%B8%8A%E9%82%A3%E4%B8%80%E5%A0%B5%E7%89%86%E6%95%B8%E5%AD%97%E8%83%8C%E5%BE%8C%E7%9A%84%E7%89%A9%E7%90%86%E6%A5%B5%E9%99%90" class="hash-link" aria-label="撞上那一堵牆：數字背後的物理極限的直接連結" title="撞上那一堵牆：數字背後的物理極限的直接連結" translate="no">​</a></h2>
<p>這場失敗的核心原因，可以總結成一組冷冰冰但現實的數字：<strong>3MB 與 25MB</strong>。</p>
<p>在 Cloudflare Pages / Workers 的環境下，有兩個我們無法迴避的硬指標：</p>
<ol>
<li class=""><strong>單一腳本限制 (3MB)</strong>：每個 Worker 的腳本大小（未壓縮）不能超過 3MB。當我們使用 <code>@opennextjs/cloudflare</code> 將 Next.js 編譯後，最重要的伺服器端邏輯（包括那些重型的 SDK）都會被打包進這個單一文件中。</li>
<li class=""><strong>全專案總合限制 (25MB)</strong>：如果你使用的是免費版，整個專案的靜態資產與腳本總合也有 25MB 的上限。對於一個內建了大量視覺組件與第三方庫的「旗艦版」CMS 來說，這個數字比想像中更脆弱。</li>
</ol>
<p>雖然 <strong>付費版 (Paid/Standard)</strong> 可以將單一腳本限制放寬到 <strong>10MB</strong>，但這通常意味著你需要每個月支付額外的費用，且依然要面對代碼膨脹導致的冷啟動（Cold Start）延遲問題。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-環境的基因差異-the-vercel-lock-in">3. 環境的基因差異 (The Vercel Lock-in)<a href="https://garden.liwen.studio/blog/nextjs-deployment-cloudflare-pages-struggle#3-%E7%92%B0%E5%A2%83%E7%9A%84%E5%9F%BA%E5%9B%A0%E5%B7%AE%E7%95%B0-the-vercel-lock-in" class="hash-link" aria-label="3. 環境的基因差異 (The Vercel Lock-in)的直接連結" title="3. 環境的基因差異 (The Vercel Lock-in)的直接連結" translate="no">​</a></h3>
<p>除了數字上的限制，我們更深刻感受到的是「基因」的不同。Next.js 作為 Vercel 的親兒子，其 App Router 的許多行為本質上是為其原生平台量身打造的。搬家到 Cloudflare 不只是換個伺服器，更是一場「去 Vercel 化」的架構重構，這種框架與平台間的深度耦合，讓我們在遷移過程中如履薄冰。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-sdk-的重量">1. SDK 的重量<a href="https://garden.liwen.studio/blog/nextjs-deployment-cloudflare-pages-struggle#1-sdk-%E7%9A%84%E9%87%8D%E9%87%8F" class="hash-link" aria-label="1. SDK 的重量的直接連結" title="1. SDK 的重量的直接連結" translate="no">​</a></h3>
<p>我們的應用中包含了一些功能強大但也「沉重」的工具：</p>
<ul>
<li class=""><strong>Shiki</strong>: 用於極致的代碼高亮。</li>
<li class=""><strong>React-Markdown / Rehype</strong>: 用於處理內容渲染。</li>
<li class=""><strong>Resend</strong>: 郵件服務。</li>
<li class=""><strong>Supabase SDK</strong>: 數據庫交互的核心。</li>
</ul>
<p>在 Vercel 上，這些 SDK 運行在 Node.js 環境中，幾乎沒有大小限制。但在 Cloudflare Edge 環境下，所有這些都必須塞進那個小小的 Worker 腳本裡。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-環境適應不良-nodejs-vs-edge-runtime">2. 環境適應不良 (Node.js vs Edge Runtime)<a href="https://garden.liwen.studio/blog/nextjs-deployment-cloudflare-pages-struggle#2-%E7%92%B0%E5%A2%83%E9%81%A9%E6%87%89%E4%B8%8D%E8%89%AF-nodejs-vs-edge-runtime" class="hash-link" aria-label="2. 環境適應不良 (Node.js vs Edge Runtime)的直接連結" title="2. 環境適應不良 (Node.js vs Edge Runtime)的直接連結" translate="no">​</a></h3>
<p>Next.js 許多方便的功能（如某些特定模組的伺服器端渲染）依賴於完整的 Node.js API，而 Cloudflare 的 Edge Runtime 則是基於 V8 的受限環境。雖然 <code>open-next</code> 已經做了很多努力，但要讓整個 App 完美適應，需要大量的重構。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我們嘗試過的解決方案那些我們走過的彎路">我們嘗試過的解決方案（那些我們走過的彎路）<a href="https://garden.liwen.studio/blog/nextjs-deployment-cloudflare-pages-struggle#%E6%88%91%E5%80%91%E5%98%97%E8%A9%A6%E9%81%8E%E7%9A%84%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88%E9%82%A3%E4%BA%9B%E6%88%91%E5%80%91%E8%B5%B0%E9%81%8E%E7%9A%84%E5%BD%8E%E8%B7%AF" class="hash-link" aria-label="我們嘗試過的解決方案（那些我們走過的彎路）的直接連結" title="我們嘗試過的解決方案（那些我們走過的彎路）的直接連結" translate="no">​</a></h2>
<p>雖然結果是失敗，但我們嘗試了所有能想到的優化手段：</p>
<ol>
<li class="">
<p><strong>動態導入 (Dynamic Imports)</strong>：
我們嘗試將重型的組件（如語法高亮的編輯器）改為 <code>next/dynamic</code> 僅在客戶端載入。這確實減小了 Server-side 的壓力，但對於某些強依賴服務端邏輯的頁面，優化空間依然有限。</p>
</li>
<li class="">
<p><strong>SDK 脫水 (SDK Stripping)</strong>：
我們嘗試移除某些非核心的重型 SDK，改用更輕量（但功能較少）的替代品。但對於追求「旗艦體驗」的我們來說，這種權衡（Trade-off）降低了產品的質感。</p>
</li>
<li class="">
<p><strong>OpenNext 精確配置</strong>：
調整 <code>open-next.config</code> 來排除某些不必要的 chunks，但 Next.js App Router 的構建邏輯有時像個黑盒子，難以精確拆分。</p>
</li>
<li class="">
<p><strong>與「看不見」的細節對抗</strong>：
我們在環境變數（Secrets）的遷移和 <strong>圖片優化 (next/image)</strong> 的配置上也耗費了大量心力。在 Vercel 點點介面就能搞定的事，在 Cloudflare 需要透過 <code>wrangler.toml</code> 重新佈署，甚至要額外建立 R2 儲存桶來支撐圖片處理。</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="給下一個挑戰者的-3-個建議">給下一個挑戰者的 3 個建議<a href="https://garden.liwen.studio/blog/nextjs-deployment-cloudflare-pages-struggle#%E7%B5%A6%E4%B8%8B%E4%B8%80%E5%80%8B%E6%8C%91%E6%88%B0%E8%80%85%E7%9A%84-3-%E5%80%8B%E5%BB%BA%E8%AD%B0" class="hash-link" aria-label="給下一個挑戰者的 3 個建議的直接連結" title="給下一個挑戰者的 3 個建議的直接連結" translate="no">​</a></h2>
<p>如果你也正打算將 Next.js 應用部署到 Cloudflare，請務必先確認以下事項：</p>
<ol>
<li class=""><strong>模組化你的依賴</strong>：在引入任何庫（Library）之前，先去 <a href="https://bundlephobia.com/" target="_blank" rel="noopener noreferrer" class="">BundlePhobia</a> 看看它的體積。</li>
<li class=""><strong>優先考慮 Edge-first</strong>：從開發第一天起就以 Edge Runtime 為基準。如果你是到了開發中後期才決定切換環境，那代價可能會非常沉重。</li>
<li class=""><strong>功能權衡</strong>：如果你的應用需要大量重型的 SSR 渲染或依賴傳統 Node.js 庫，那麼 Vercel 或 Netlify 或許才是你更溫暖的家。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="結語失敗是另一種形式的累積">結語：失敗是另一種形式的累積<a href="https://garden.liwen.studio/blog/nextjs-deployment-cloudflare-pages-struggle#%E7%B5%90%E8%AA%9E%E5%A4%B1%E6%95%97%E6%98%AF%E5%8F%A6%E4%B8%80%E7%A8%AE%E5%BD%A2%E5%BC%8F%E7%9A%84%E7%B4%AF%E7%A9%8D" class="hash-link" aria-label="結語：失敗是另一種形式的累積的直接連結" title="結語：失敗是另一種形式的累積的直接連結" translate="no">​</a></h2>
<p>雖然這次部署失敗了，但我們對於 LiwenBlog CMS 的性能邊界有了更深刻的認識。有些美是需要空間支撐的，而 Cloudflare 的邊緣計算目前更像是一個「精而小」的藝術館，不適合塞進所有的東西。</p>
<p>我們回到了熟悉的環境，繼續打磨我們的產品。下次再挑戰時，我們會帶著更精簡、更具適應性的代碼再次出發。</p>]]></content:encoded>
            <category>cloudflare</category>
            <category>NextJs</category>
            <category>雲端部署</category>
        </item>
        <item>
            <title><![CDATA[冒名頂替症候群 (Imposter Syndrome) 深度解析]]></title>
            <link>https://garden.liwen.studio/blog/imposter-syndrome</link>
            <guid>https://garden.liwen.studio/blog/imposter-syndrome</guid>
            <pubDate>Tue, 21 Apr 2026 06:46:36 GMT</pubDate>
            <description><![CDATA[深入探討冒名頂替症候群的定義、五種類型以及實用的心態調整方式，幫助開發者克服內心不安，建立穩健的專業自信。]]></description>
            <content:encoded><![CDATA[
<p><a href="https://gemini.google.com/share/419d34c2d3f6" target="_blank" rel="noopener noreferrer" class="">互動網站</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一-定義什麼是冒名頂替症候群">一、 定義：什麼是冒名頂替症候群？<a href="https://garden.liwen.studio/blog/imposter-syndrome#%E4%B8%80-%E5%AE%9A%E7%BE%A9%E4%BB%80%E9%BA%BC%E6%98%AF%E5%86%92%E5%90%8D%E9%A0%82%E6%9B%BF%E7%97%87%E5%80%99%E7%BE%A4" class="hash-link" aria-label="一、 定義：什麼是冒名頂替症候群？的直接連結" title="一、 定義：什麼是冒名頂替症候群？的直接連結" translate="no">​</a></h2>
<p>冒名頂替症候群最早由心理學家 <strong>Pauline Clance</strong> 與 <strong>Suzanne Imes</strong> 於 1978 年提出。它並非一種精神疾病，而是一種<strong>心理現象</strong>。</p>
<ul>
<li class=""><strong>核心特徵：</strong> 個體無法將自己的成功歸因於能力，儘管有外部證據（如證書、好評、晉升），仍深信自己是「騙子」，認為成功只是因為運氣、時機或誤導了他人。</li>
<li class=""><strong>心理運作：</strong> 這種焦慮通常伴隨著「害怕被拆穿」的恐懼，覺得自己不配擁有目前的地位。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二-常見的情況與徵兆">二、 常見的情況與徵兆<a href="https://garden.liwen.studio/blog/imposter-syndrome#%E4%BA%8C-%E5%B8%B8%E8%A6%8B%E7%9A%84%E6%83%85%E6%B3%81%E8%88%87%E5%BE%B5%E5%85%86" class="hash-link" aria-label="二、 常見的情況與徵兆的直接連結" title="二、 常見的情況與徵兆的直接連結" translate="no">​</a></h2>
<p>你可以對照以下表格，檢視自己是否有相關的心理感受：</p>
<table><thead><tr><th style="text-align:left">情況</th><th style="text-align:left">內心獨白示例</th><th style="text-align:left">行為表現</th></tr></thead><tbody><tr><td style="text-align:left"><strong>過度準備</strong></td><td style="text-align:left">「我必須比別人多花三倍時間準備，否則一定會搞砸。」</td><td style="text-align:left">容易導致精疲力竭 (Burnout)。</td></tr><tr><td style="text-align:left"><strong>忽視成就</strong></td><td style="text-align:left">「那份報告會成功只是因為主管心情好，沒什麼了不起。」</td><td style="text-align:left">將成功過度歸因於運氣或他人因素。</td></tr><tr><td style="text-align:left"><strong>害怕挑戰</strong></td><td style="text-align:left">「如果我接下這個新專案，大家就會發現我其實什麼都不懂。」</td><td style="text-align:left">為了避免曝露「無能」而逃避晉升機會。</td></tr><tr><td style="text-align:left"><strong>社交退縮</strong></td><td style="text-align:left">在厲害的人身邊不敢發言，擔心說錯話會顯露無知。</td><td style="text-align:left">隱藏真實觀點，僅採取安全的回應。</td></tr><tr><td style="text-align:left"><strong>完美主義</strong></td><td style="text-align:left">「只要有一點小錯誤，就覺得自己之前的努力全都白費了。」</td><td style="text-align:left">對自我評價極端，難以容忍合理的錯誤。</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三-進階補充五種冒名頂替者類型-the-5-types">三、 進階補充：五種冒名頂替者類型 (The 5 Types)<a href="https://garden.liwen.studio/blog/imposter-syndrome#%E4%B8%89-%E9%80%B2%E9%9A%8E%E8%A3%9C%E5%85%85%E4%BA%94%E7%A8%AE%E5%86%92%E5%90%8D%E9%A0%82%E6%9B%BF%E8%80%85%E9%A1%9E%E5%9E%8B-the-5-types" class="hash-link" aria-label="三、 進階補充：五種冒名頂替者類型 (The 5 Types)的直接連結" title="三、 進階補充：五種冒名頂替者類型 (The 5 Types)的直接連結" translate="no">​</a></h2>
<p>根據 Valerie Young 的研究，冒名頂替症候群可以細分為五種典型，了解自己屬於哪一類有助於更精準地調整心態：</p>
<ol>
<li class=""><strong>完美主義者 (The Perfectionist)：</strong> 設定過高目標，即便達成 99% 仍會為了 1% 的瑕疵感到失敗。</li>
<li class=""><strong>超人 (The Superhuman)：</strong> 試圖在工作、家庭、社交等所有領域都達到頂尖，以此掩飾內心的不安。</li>
<li class=""><strong>天生天才 (The Natural Genius)：</strong> 習慣於迅速掌握新事物，若遇到需要努力苦練才能學會的事，會覺得自己「沒天賦、是騙子」。</li>
<li class=""><strong>獨行俠 (The Soloist)：</strong> 認為必須獨立完成所有事，請求幫助在他們眼中等同於「無能」。</li>
<li class=""><strong>專家 (The Expert)：</strong> 永遠覺得自己懂的不夠多，即便已非常有經驗，仍會不斷瘋狂進修以求安全感。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四-實用的解決方式">四、 實用的解決方式<a href="https://garden.liwen.studio/blog/imposter-syndrome#%E5%9B%9B-%E5%AF%A6%E7%94%A8%E7%9A%84%E8%A7%A3%E6%B1%BA%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="四、 實用的解決方式的直接連結" title="四、 實用的解決方式的直接連結" translate="no">​</a></h2>
<p>克服此症候群並非要「消除」感覺，而是學習與之共處，並減少它對行動的阻礙。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-區分感覺與事實">1. 區分「感覺」與「事實」<a href="https://garden.liwen.studio/blog/imposter-syndrome#1-%E5%8D%80%E5%88%86%E6%84%9F%E8%A6%BA%E8%88%87%E4%BA%8B%E5%AF%A6" class="hash-link" aria-label="1. 區分「感覺」與「事實」的直接連結" title="1. 區分「感覺」與「事實」的直接連結" translate="no">​</a></h3>
<p>這是最重要的第一步。</p>
<ul>
<li class=""><strong>感覺：</strong> 「我覺得自己很弱，大家都在笑我。」</li>
<li class=""><strong>事實：</strong> 「我準時提交了專案，且目前沒有收到任何負面反饋。」</li>
<li class=""><strong>行動：</strong> 當焦慮來襲，試著寫下負面情緒，並在旁邊列出客觀數據來反駁。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-建立成就紀錄本-the-brag-document">2. 建立「成就紀錄本」 (The Brag Document)<a href="https://garden.liwen.studio/blog/imposter-syndrome#2-%E5%BB%BA%E7%AB%8B%E6%88%90%E5%B0%B1%E7%B4%80%E9%8C%84%E6%9C%AC-the-brag-document" class="hash-link" aria-label="2. 建立「成就紀錄本」 (The Brag Document)的直接連結" title="2. 建立「成就紀錄本」 (The Brag Document)的直接連結" translate="no">​</a></h3>
<p>人的大腦有「負面偏向」，容易忘記成功。</p>
<ul>
<li class=""><strong>記錄內容：</strong> 解決過的難題、學會的新技能、獲得的讚賞。</li>
<li class=""><strong>作用：</strong> 當你懷疑自己時，翻開紀錄，讓過去的成就成為現在的後盾。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-與他人分享感受">3. 與他人分享感受<a href="https://garden.liwen.studio/blog/imposter-syndrome#3-%E8%88%87%E4%BB%96%E4%BA%BA%E5%88%86%E4%BA%AB%E6%84%9F%E5%8F%97" class="hash-link" aria-label="3. 與他人分享感受的直接連結" title="3. 與他人分享感受的直接連結" translate="no">​</a></h3>
<p>冒名頂替症候群在「沈默」中長得最快。</p>
<ul>
<li class=""><strong>破除神話：</strong> 當你發現身邊的「大神」也會感到焦慮時，壓力會大幅減輕。</li>
<li class=""><strong>尋找導師：</strong> 與信任的 Mentor 分享，從客觀第三者的視角重新評估你的價值。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-重新定義失敗擁抱成長型思維">4. 重新定義失敗：擁抱「成長型思維」<a href="https://garden.liwen.studio/blog/imposter-syndrome#4-%E9%87%8D%E6%96%B0%E5%AE%9A%E7%BE%A9%E5%A4%B1%E6%95%97%E6%93%81%E6%8A%B1%E6%88%90%E9%95%B7%E5%9E%8B%E6%80%9D%E7%B6%AD" class="hash-link" aria-label="4. 重新定義失敗：擁抱「成長型思維」的直接連結" title="4. 重新定義失敗：擁抱「成長型思維」的直接連結" translate="no">​</a></h3>
<ul>
<li class="">將「我不會」改為 <strong>「我還沒學會」</strong>。</li>
<li class=""><strong>核心價值：</strong> 專業人士的價值不在於無所不知，而在於<strong>具備找到答案的能力</strong>。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-檢視環境因素-補充">5. 檢視環境因素 (補充)<a href="https://garden.liwen.studio/blog/imposter-syndrome#5-%E6%AA%A2%E8%A6%96%E7%92%B0%E5%A2%83%E5%9B%A0%E7%B4%A0-%E8%A3%9C%E5%85%85" class="hash-link" aria-label="5. 檢視環境因素 (補充)的直接連結" title="5. 檢視環境因素 (補充)的直接連結" translate="no">​</a></h3>
<p>有時這種感覺源於「環境」。若身處競爭過激烈或缺乏多元包容性的職場（如少數族裔、女性在傳統男性主導行業），這種心理感會被放大。理解這並非單純個人問題，有助於緩解自我批判。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="來源與參考資料">來源與參考資料<a href="https://garden.liwen.studio/blog/imposter-syndrome#%E4%BE%86%E6%BA%90%E8%88%87%E5%8F%83%E8%80%83%E8%B3%87%E6%96%99" class="hash-link" aria-label="來源與參考資料的直接連結" title="來源與參考資料的直接連結" translate="no">​</a></h2>
<ul>
<li class=""><strong>Clance, P. R., &amp; Imes, S. A. (1978):</strong> <em>The imposter phenomenon in high achieving women: Dynamics and therapeutic intervention.</em></li>
<li class=""><strong>Valerie Young (2011):</strong> <em>The Secret Thoughts of Successful Women.</em></li>
<li class=""><strong>Harvard Business Review:</strong> <a href="https://hbr.org/" target="_blank" rel="noopener noreferrer" class="">Stop Telling Women They Have Imposter Syndrome</a></li>
</ul>]]></content:encoded>
            <category>心理學</category>
            <category>職涯發展</category>
        </item>
        <item>
            <title><![CDATA[工程師評價心理學：從內在計分卡奪回成長主導權]]></title>
            <link>https://garden.liwen.studio/blog/engineer-evaluation-psychology</link>
            <guid>https://garden.liwen.studio/blog/engineer-evaluation-psychology</guid>
            <pubDate>Tue, 21 Apr 2026 06:46:35 GMT</pubDate>
            <description><![CDATA[深入探討工程師在面對職場評價時的心理機制（如生存本能、鏡像自我），並提供建立內在計分卡、自我與代碼解耦的實戰心法。]]></description>
            <content:encoded><![CDATA[<p>互動網站: <a href="https://gemini.google.com/share/6a9569c51762" target="_blank" rel="noopener noreferrer" class="">https://gemini.google.com/share/6a9569c51762</a></p>
<p>這份筆記整理了工程師在職場中面對「評價」時的心理機制、常見行為模式，以及如何建立健康的自我認同體系。</p>
<!-- -->
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一核心問題為什麼我們如此在意評價">一、核心問題：為什麼我們如此在意評價？<a href="https://garden.liwen.studio/blog/engineer-evaluation-psychology#%E4%B8%80%E6%A0%B8%E5%BF%83%E5%95%8F%E9%A1%8C%E7%82%BA%E4%BB%80%E9%BA%BC%E6%88%91%E5%80%91%E5%A6%82%E6%AD%A4%E5%9C%A8%E6%84%8F%E8%A9%95%E5%83%B9" class="hash-link" aria-label="一、核心問題：為什麼我們如此在意評價？的直接連結" title="一、核心問題：為什麼我們如此在意評價？的直接連結" translate="no">​</a></h2>
<p>工程師的工作產出（代碼、架構）高度抽象且具備創造性。當產出受到批評時，大腦往往無法區分「對工作的建議」與「對個人的否定」。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="心理學三大根源">心理學三大根源<a href="https://garden.liwen.studio/blog/engineer-evaluation-psychology#%E5%BF%83%E7%90%86%E5%AD%B8%E4%B8%89%E5%A4%A7%E6%A0%B9%E6%BA%90" class="hash-link" aria-label="心理學三大根源的直接連結" title="心理學三大根源的直接連結" translate="no">​</a></h3>
<ul>
<li class="">
<p><strong>生存本能（Survival Instinct）</strong><br>
<!-- -->在演化過程中，被部落排斥意味著死亡。大腦邊緣系統會將「負面評價」視為物理威脅，觸發皮質醇分泌，導致心跳加速與焦慮。</p>
</li>
<li class="">
<p><strong>鏡像自我（Looking-glass Self）</strong><br>
<!-- -->查爾斯·庫利（Charles Cooley）提出。我們對自己的定義，其實是我們觀察到「別人如何看待我」的投影。評價成了我們確認自身能力的座標。</p>
</li>
<li class="">
<p><strong>社會比較理論（Social Comparison Theory）</strong><br>
<!-- -->利昂·費斯廷格（Leon Festinger）提出。在缺乏絕對標準時，人類傾向透過與同儕比較（如職級、薪資）來定義成功。</p>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二工程師日常生動案例分析">二、工程師日常生動案例分析<a href="https://garden.liwen.studio/blog/engineer-evaluation-psychology#%E4%BA%8C%E5%B7%A5%E7%A8%8B%E5%B8%AB%E6%97%A5%E5%B8%B8%E7%94%9F%E5%8B%95%E6%A1%88%E4%BE%8B%E5%88%86%E6%9E%90" class="hash-link" aria-label="二、工程師日常生動案例分析的直接連結" title="二、工程師日常生動案例分析的直接連結" translate="no">​</a></h2>
<p>透過具體場景，識別這些心理機制如何影響開發者的行為：</p>
<table><thead><tr><th>案例角色</th><th>核心情境</th><th>心理分析</th></tr></thead><tbody><tr><td><strong>老王的 PR 焦慮</strong></td><td>收到 Request Changes 或「邏輯笨」的評論</td><td>外在計分卡主導。將代碼品質與智力掛鉤，導致防禦心態</td></tr><tr><td><strong>小美的技術債恐懼</strong></td><td>擔心改壞代碼影響「穩重」形象，故不敢重構</td><td>生存本能與社交安全性。為了維持零失誤評價而犧牲技術價值</td></tr><tr><td><strong>阿強的技術焦慮</strong></td><td>盲目引入大廠新框架，追求技術流行</td><td>社會比較理論。在跟「社群幻覺」比較，試圖透過工具換取認可</td></tr><tr><td><strong>大力的救火英雄</strong></td><td>享受半夜第一個修 Bug 的「被需要感」</td><td>效用認可沉迷。成功建立在被需要的頻率，而非長期產出影響力</td></tr><tr><td><strong>曉芬的文檔完美主義</strong></td><td>花費過多時間潤飾文檔，擔心一個錯字毀專業感</td><td>完美主義下的評價權放大。在邊際效益極低的工作上消耗過度</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三實踐解決方案奪回評價主導權">三、實踐解決方案：奪回評價主導權<a href="https://garden.liwen.studio/blog/engineer-evaluation-psychology#%E4%B8%89%E5%AF%A6%E8%B8%90%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88%E5%A5%AA%E5%9B%9E%E8%A9%95%E5%83%B9%E4%B8%BB%E5%B0%8E%E6%AC%8A" class="hash-link" aria-label="三、實踐解決方案：奪回評價主導權的直接連結" title="三、實踐解決方案：奪回評價主導權的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-建立內在計分卡inner-scorecard">1. 建立「內在計分卡」（Inner Scorecard）<a href="https://garden.liwen.studio/blog/engineer-evaluation-psychology#1-%E5%BB%BA%E7%AB%8B%E5%85%A7%E5%9C%A8%E8%A8%88%E5%88%86%E5%8D%A1inner-scorecard" class="hash-link" aria-label="1. 建立「內在計分卡」（Inner Scorecard）的直接連結" title="1. 建立「內在計分卡」（Inner Scorecard）的直接連結" translate="no">​</a></h3>
<p>華倫·巴菲特（Warren Buffett）提出的概念。重點在於你是用「自己的標準」還是「別力的標準」來衡量成功。</p>
<ul>
<li class=""><strong>內在指標</strong>：今天的我比三個月前更進步嗎？我解決了真實的問題嗎？</li>
<li class=""><strong>外在指標</strong>：GitHub Stars、主管的評分字母、名片上的頭銜。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-自我與代碼的解耦decoupling">2. 自我與代碼的解耦（Decoupling）<a href="https://garden.liwen.studio/blog/engineer-evaluation-psychology#2-%E8%87%AA%E6%88%91%E8%88%87%E4%BB%A3%E7%A2%BC%E7%9A%84%E8%A7%A3%E8%80%A6decoupling" class="hash-link" aria-label="2. 自我與代碼的解耦（Decoupling）的直接連結" title="2. 自我與代碼的解耦（Decoupling）的直接連結" translate="no">​</a></h3>
<ul>
<li class=""><strong>心態重構</strong>：代碼離開編輯器後，它就是一個獨立的邏輯實體。</li>
<li class=""><strong>定義</strong>：被 Review 的是「代碼的時空複雜度」，而不是「你的人格」。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-擁抱凡人化心態">3. 擁抱「凡人化心態」<a href="https://garden.liwen.studio/blog/engineer-evaluation-psychology#3-%E6%93%81%E6%8A%B1%E5%87%A1%E4%BA%BA%E5%8C%96%E5%BF%83%E6%85%8B" class="hash-link" aria-label="3. 擁抱「凡人化心態」的直接連結" title="3. 擁抱「凡人化心態」的直接連結" translate="no">​</a></h3>
<ul>
<li class="">承認自己也有不懂的地方，這並不會損害資深工程師的威信。</li>
<li class="">主動提問能建立團隊的「心理安全性」，讓溝通回到解決問題的本質。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四總結與座右銘">四、總結與座右銘<a href="https://garden.liwen.studio/blog/engineer-evaluation-psychology#%E5%9B%9B%E7%B8%BD%E7%B5%90%E8%88%87%E5%BA%A7%E5%8F%B3%E9%8A%98" class="hash-link" aria-label="四、總結與座右銘的直接連結" title="四、總結與座右銘的直接連結" translate="no">​</a></h2>
<blockquote>
<p><strong>「評價是一項工具，而不應是你的主人。」</strong></p>
</blockquote>
<p>當你專注於內在成長時，外部的認可會作為「副產品」自然隨之而來，而不再是你焦慮的源頭。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="參考來源及進階閱讀">參考來源及進階閱讀<a href="https://garden.liwen.studio/blog/engineer-evaluation-psychology#%E5%8F%83%E8%80%83%E4%BE%86%E6%BA%90%E5%8F%8A%E9%80%B2%E9%9A%8E%E9%96%B1%E8%AE%80" class="hash-link" aria-label="參考來源及進階閱讀的直接連結" title="參考來源及進階閱讀的直接連結" translate="no">​</a></h2>
<ul>
<li class=""><strong>Festinger, L. (1954):</strong> <em>A theory of social comparison processes.</em> （社會比較理論）</li>
<li class=""><strong>Cooley, C. H. (1902):</strong> <em>Human Nature and the Social Order.</em> （鏡像自我理論）</li>
<li class=""><strong>Schroeder, A. (2008):</strong> <em>The Snowball.</em> （巴菲特傳記：內在計分卡概念）</li>
<li class=""><strong>Dweck, C. S. (2006):</strong> <em>Mindset: The New Psychology of Success.</em> （成長型思維）</li>
<li class=""><strong>Brooks, F. P. (1975):</strong> <em>The Mythical Man-Month.</em> （軟體工程心理協作）</li>
</ul>]]></content:encoded>
            <category>心理學</category>
            <category>職涯發展</category>
            <category>工程文化</category>
        </item>
        <item>
            <title><![CDATA[身為全公司的避風港：MIS 的無奈日常與工作邊界感]]></title>
            <link>https://garden.liwen.studio/blog/mis-daily-rant</link>
            <guid>https://garden.liwen.studio/blog/mis-daily-rant</guid>
            <pubDate>Tue, 21 Apr 2026 06:46:35 GMT</pubDate>
            <description><![CDATA[從一場突如其來的亞馬遜賣場求救事件，反思 MIS 職位在職場中被當成『避風港』的依賴心理，並探討如何建立工作邊界與專業所有權 (Ownership)。]]></description>
            <content:encoded><![CDATA[<p>這原本應該是個美好的補眠日。因為前一天沒睡好，索性請了一天假，打算好好充電後，再來專心處理最近一直沒空執行的資訊需求。沒想到，美好的早晨在八點多就被無情打斷。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="在最後一刻才求救的亞馬遜賣場事件">在最後一刻才求救的「亞馬遜賣場」事件<a href="https://garden.liwen.studio/blog/mis-daily-rant#%E5%9C%A8%E6%9C%80%E5%BE%8C%E4%B8%80%E5%88%BB%E6%89%8D%E6%B1%82%E6%95%91%E7%9A%84%E4%BA%9E%E9%A6%AC%E9%81%9C%E8%B3%A3%E5%A0%B4%E4%BA%8B%E4%BB%B6" class="hash-link" aria-label="在最後一刻才求救的「亞馬遜賣場」事件的直接連結" title="在最後一刻才求救的「亞馬遜賣場」事件的直接連結" translate="no">​</a></h2>
<p>一大早被吵醒，居然是因為行銷部門今天要開賣場，才發現當初申請的亞馬遜帳號還需要再申請 Seller 的相關認證。這讓我滿頭問號：幾個月前帳號申請完不就已經移交了嗎？為什麼現在還要我來幫忙申請？</p>
<p>如果是我負責一個專案，拿到帳號的第一天，我一定會先登入進去點點看，確認能不能順利開賣場、熟悉一下流程。我實在無法理解，為什麼總是有人喜歡拖到「最後關頭」才在那邊驚慌求救，真的是會讓人理智線斷裂。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="缺東缺西的資料整理">缺東缺西的資料整理<a href="https://garden.liwen.studio/blog/mis-daily-rant#%E7%BC%BA%E6%9D%B1%E7%BC%BA%E8%A5%BF%E7%9A%84%E8%B3%87%E6%96%99%E6%95%B4%E7%90%86" class="hash-link" aria-label="缺東缺西的資料整理的直接連結" title="缺東缺西的資料整理的直接連結" translate="no">​</a></h2>
<p>本來想說直接把這件事放生不理，轉頭準備來專心處理我的資訊需求。結果一打開資料，又是一陣血壓飆升：資料缺東缺西，該連結的資料完全沒有對應起來。</p>
<p>我就問，這不過才 30 幾筆資料而已！操作邏輯不就是點個編輯、在下拉式選單選好分類、然後按下儲存嗎？這困難點到底在哪裡？</p>
<p>結果對方還說要再拉一個人進來確認。到底要確認什麼？介面上全部都是中文字！然後又開始扯說「我的代號跟他們習慣的不一樣」……阿你選下拉選單的時候明明看到的是中文啊，這到底關代號什麼事？</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="mis-不是沒有底線的避風港">MIS 不是沒有底線的「避風港」<a href="https://garden.liwen.studio/blog/mis-daily-rant#mis-%E4%B8%8D%E6%98%AF%E6%B2%92%E6%9C%89%E5%BA%95%E7%B7%9A%E7%9A%84%E9%81%BF%E9%A2%A8%E6%B8%AF" class="hash-link" aria-label="MIS 不是沒有底線的「避風港」的直接連結" title="MIS 不是沒有底線的「避風港」的直接連結" translate="no">​</a></h2>
<p>總之，今天就是充斥著這些令人無語的鳥事。</p>
<p>越想越覺得，待在這裡，我好像不知不覺變成了全公司的「避風港」。彷彿只要跟「電腦、系統、帳號」沾上一點邊，把東西往 MIS 身上一丟，剩下的事情就全部與他們無關了。</p>
<p>這不只是工作技能的差異，更多的是對自己工作業務<strong>缺乏所有權 (Ownership)</strong> 與<strong>責任感</strong>的問題。當每個人都預期「反正出包了、不會用了，最後 MIS 會幫忙擦屁股」，這種依賴心理就會無止盡地蔓延。</p>
<p>是時候重新建立工作上的邊界感了。不能再讓「太好說話」變成別人推卸責任的理由。</p>]]></content:encoded>
            <category>職場</category>
            <category>工程文化</category>
            <category>工作心法</category>
        </item>
    </channel>
</rss>