<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Pan in the Nutshell</title><link>https://blog.pan93.com/</link><description>Recent content on Pan in the Nutshell</description><generator>Hugo -- 0.154.5</generator><language>zh-tw</language><lastBuildDate>Sun, 28 Sep 2025 17:54:18 +0800</lastBuildDate><atom:link href="https://blog.pan93.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Next.js 的渲染模式是怎麼運作的：SSR、CSR、SSG、ISR、PPR…</title><link>https://blog.pan93.com/posts/how-nextjs-rendering-works/</link><pubDate>Sun, 28 Sep 2025 17:54:18 +0800</pubDate><guid>https://blog.pan93.com/posts/how-nextjs-rendering-works/</guid><description>&lt;blockquote&gt;
&lt;p&gt;看了一下 next.js ，感覺只是將原本 react 提供的 js 檔案先轉成 html 再輸出，還是獨立伺服器處理，不像 express-handlebars 可以跟 node.js 整合在一台伺服器內 [&lt;a href="https://t.me/moztw_general/1/263038"&gt;MozTW&lt;/a&gt;]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我不太喜歡把 Next.js 的渲染模式歸結成任何形式 (SSR or CSR or SSG)，但想和你分享一下為什麼 Next.js 不是另一個 express-handlebars，以及 Next.js 團隊到底在渲染下做了多少事情。&lt;/p&gt;
&lt;p&gt;express-handlebars 通常是純粹的 SSR，不考慮你在頁面中插入的 JavaScript，handlebars 不用往產物增加 JavaScript 恢復互動元素（也就是 Hydration）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hydration 是指 React 接管由伺服器產生的靜態 HTML、並為其附加事件監聽器與狀態，使其從無生命的骨架變成可互動應用的關鍵步驟。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;純粹的 React，就以搭配 Vite 來說的話，是 CSR。因為背後不用有配合的伺服器，放在可以 host 靜態網站的 web server 上就可以（比如 NGINX、Caddy 這種）。&lt;/p&gt;
&lt;h2 id="nextjs-的-pages-router-有哪些渲染方式"&gt;Next.js 的 Pages Router 有哪些渲染方式？&lt;/h2&gt;
&lt;p&gt;接下來回頭看 Next.js。如果只看 Pages Router，會相對簡單一點，可以分成 SSG、ISR、SSR、CSR 的部分：&lt;/p&gt;
&lt;p&gt;如果頁面不用在伺服器端請求資訊，而且要產生的頁面是可以在編譯期推斷出來的，Next.js 會在編譯期 (build time) 產生好 HTML，使用者瀏覽時就會直接回傳，這叫 SSG (Static Site Generation)&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;</description></item><item><title>如何繞過 Vercel 的 Function 逾時？</title><link>https://blog.pan93.com/posts/vercel-function-timeout-workaround/</link><pubDate>Sun, 21 Sep 2025 13:39:02 +0800</pubDate><guid>https://blog.pan93.com/posts/vercel-function-timeout-workaround/</guid><description>&lt;p&gt;&lt;a href="https://www.threads.com/@ruei_hua7th/post/DK7RWrLpgYV?xmt=AQF0ri-Ztw3Ofz9nb8FjUyki5lWSTc9FYgDZauUA5daUDg"&gt;&lt;img alt="original post" loading="lazy" src="https://assets.blog.pan93.com/vercel-function-timeout-workaround/original-post-optimised.avif"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;執行逾時了，Vercel 的免費方案 &lt;a href="https://vercel.com/docs/fluid-compute#default-settings-by-plan"&gt;只給你 60 秒回應&lt;/a&gt;。假如你的 /upload 等待回應的時間超過 60 秒，那就會被 Vercel 殺掉哦。&lt;/p&gt;
&lt;p&gt;有三種解法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;如果不想花錢，就想辦法把任務放到背景，如圖一。不過我翻了一下 Vercel 的文件，它沒給你機會建立一個超過 60 秒的任務，所以你大概得在 Cloudflare Workers 用 JavaScript 重寫你 Gemini 等待回應那段邏輯，用 waitUntil 把任務移到背景執行，接著將回應寫進資料庫，你的前端再不停呼叫 response 等回應。注意 Cloudflare Workers 的免費方案有 100,000 次的觸發限制，而一輪會至少觸發 2 次。&lt;/p&gt;
&lt;p&gt;&lt;img alt="workaround" loading="lazy" src="https://assets.blog.pan93.com/vercel-function-timeout-workaround/workaround-optimised.webp"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;升級到 20 美元的 Vercel Pro，他會慷慨的多給你 12 分鐘回應。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;試試看 5 美元的 Zeabur，不限制回應時間，你想處理多久就處理多久。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>「目錄」不是支語</title><link>https://blog.pan93.com/posts/folder-in-taiwanese/</link><pubDate>Sun, 21 Sep 2025 13:33:35 +0800</pubDate><guid>https://blog.pan93.com/posts/folder-in-taiwanese/</guid><description>&lt;blockquote&gt;
&lt;p&gt;今天吃飯聽到隔壁桌剛好在討論 vibe coding，然後就在講『跟 AI 說要建立「資料夾」但沒想到 AI 竟然說「目錄」？？？目錄不是支語嗎？』&lt;/p&gt;
&lt;p&gt;嚇到我了，我以前也講目錄欸，目錄真的是支語嗎？當然後來都說是資料夾了 🤔&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.threads.com/@maylogger_designer/post/DLQGPSlSVic?xmt=AQF0ri-Ztw3Ofz9nb8FjUyki5lWSTc9FYgDZauUA5daUDg"&gt;Post&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;folder（面向使用者的用詞）和 directory（檔案系統概念）的不同。無論是「資料夾」還是「目錄」，這兩個都是老資訊用詞了，都不是支語 ⚠️&lt;/p&gt;
&lt;p&gt;btw folder 在 macOS 上會翻譯成檔案夾，雖然和微軟系的「資料夾」差得有點多，但這真的不是支語 ⚠️⚠️&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;補上 Apple、Microsoft 和 GNU coreutils 對 folder / directory 的詞彙表。GNU coreutils 基本上就是常見 Linux 發行版內建指令列工具對應的翻譯。&lt;/p&gt;
&lt;p&gt;其他公司和軟體的對應翻譯也可以自己找，但不要把不認識的詞當成支語 QQ&lt;/p&gt;
&lt;p&gt;&lt;img alt="directory in Microsoft" loading="lazy" src="https://assets.blog.pan93.com/folder-in-taiwanese/directory_ms-optimised.webp"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="folder in Microsoft" loading="lazy" src="https://assets.blog.pan93.com/folder-in-taiwanese/folder_ms-optimised.webp"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="directory in Apple" loading="lazy" src="https://assets.blog.pan93.com/folder-in-taiwanese/folder_aapl-optimised.webp"&gt;&lt;/p&gt;</description></item><item><title>分頁應該在用戶端還是伺服器端進行？</title><link>https://blog.pan93.com/posts/client-vs-server-pagination/</link><pubDate>Sun, 21 Sep 2025 13:30:40 +0800</pubDate><guid>https://blog.pan93.com/posts/client-vs-server-pagination/</guid><description>&lt;blockquote&gt;
&lt;p&gt;我有看過一種做法是 api 直接在 server side 直接吐完整包列表資料，前端在自己做 pagination, 我不知道這樣做跟 api 做 pagination 比有沒有優勢&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.threads.com/@wildfrontend/post/DOLnNBQErwz?xmt=AQF0ri-Ztw3Ofz9nb8FjUyki5lWSTc9FYgDZauUA5daUDg"&gt;Post&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;原則上，如果抓資料的開銷和筆數有關，我會設計 API 層級的分頁：API 比較不需要讓資料庫查太多東西 (&lt;code&gt;SELECT&lt;/code&gt; all) 而造成資料庫壓力，也不會一次下載太多沒必要的資料回來。&lt;/p&gt;
&lt;p&gt;如果資料量沒這麼大（比如一些 Tags），我會讓 client 分頁，純粹只是為了改善前端體驗。如果資料量大一點（比如 1K），我就會傾向在 API 設計基礎的分頁功能（即便對後端來說，取多少筆資料都不會影響速度），來減少需要回傳的內容。&lt;/p&gt;
&lt;p&gt;By the way 後端分頁也有他的學問，傳統的 offset-based pagination 在資料筆數非常大的情況下，可能會有效能問題。可以看看比如 &lt;a href="https://tec.xenby.com/36-%E9%BE%90%E5%A4%A7%E8%B3%87%E6%96%99%E5%BA%AB%E5%88%86%E9%A0%81%E6%96%B9%E6%A1%88-cursor-based-pagination"&gt;Cursor-based pagination&lt;/a&gt; 的知識，或許對以後設計超大資料的分頁 API 會有幫助。&lt;/p&gt;</description></item><item><title>大量渲染不應該做太多無意義的最佳化</title><link>https://blog.pan93.com/posts/no-over-optimization-for-rendering-big-list/</link><pubDate>Sun, 21 Sep 2025 13:20:47 +0800</pubDate><guid>https://blog.pan93.com/posts/no-over-optimization-for-rendering-big-list/</guid><description>&lt;p&gt;&lt;a href="https://www.threads.com/@0mgdayvusb8oy/post/DOLk34Ykv-p?xmt=AQF0ri-Ztw3Ofz9nb8FjUyki5lWSTc9FYgDZauUA5daUDg"&gt;&lt;img alt="Original Post" loading="lazy" src="https://assets.blog.pan93.com/no-over-optimization-for-rendering-big-list/thread-original-post.avif"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;renderList&lt;/code&gt; ……？討論串很多人針對這篇評論給出很多不同建議了，我想給幾個我還沒看到的 point。&lt;/p&gt;
&lt;p&gt;關於「元件最佳化」這點：React 背後有 &lt;a href="https://hackmd.io/@Heidi-Liu/virtual-dom"&gt;Virtual DOM&lt;/a&gt;，所以你 render 100K 遍一樣的 list，相同 key、相同內容的 element 是不會在實體 DOM 層被重建的；&lt;code&gt;useMemo&lt;/code&gt; 如果 list 的內容會一直變動，反而可能會造成 memory leak。如果是新專案，我通常推薦不手寫 &lt;code&gt;useMemo&lt;/code&gt;/&lt;code&gt;useCallback&lt;/code&gt;/&lt;code&gt;memo&lt;/code&gt; 等等的記憶函式，直接讓 &lt;a href="https://react.dev/learn/react-compiler"&gt;React Compiler&lt;/a&gt; 分析哪些東西該被 memorized。&lt;/p&gt;
&lt;p&gt;既然 React 不會一直重建那 100K 個 DOM 元素，那為什麼在非常大的 list 下還是會卡？因為瀏覽器不擅長處理過多 DOM 元素。這種情況下應該要用 Virtual List 來只 render 可見元素，壞消息是他有 trade off——無法選取超出 viewport 的內容、瀏覽器內建搜尋不能搜尋全文。所以就算 GitHub 那種程式碼 view 很適合 Virtual List，但實際上都沒有實作。&lt;/p&gt;</description></item><item><title>弱 ETag 可以送到 If-Match 嗎？</title><link>https://blog.pan93.com/posts/http-weak-etag-if-match/</link><pubDate>Sun, 21 Sep 2025 13:12:21 +0800</pubDate><guid>https://blog.pan93.com/posts/http-weak-etag-if-match/</guid><description>&lt;blockquote&gt;
&lt;p&gt;如果伺服器傳送 &lt;code&gt;Etag: 1233&lt;/code&gt;，過了 CDN Client 收到 &lt;code&gt;Etag: W/&amp;quot;1233&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;那麼 Client 回送 &lt;code&gt;If-Match&lt;/code&gt; 應該傳送 &lt;code&gt;W/&amp;quot;1233&amp;quot;&lt;/code&gt; 還是 &lt;code&gt;1233&lt;/code&gt;？&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.facebook.com/share/p/17TB3cW8wC/"&gt;Original Post&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If-Match 的用意是在於防止「更新遺失」或「中途編輯衝突」（mid-air edit collision）。它的核心用途是在執行條件式請求時，確保用戶端操作的資源版本與伺服器上的目前版本完全一致&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;
&lt;p&gt;如果 CDN 傳送 &lt;code&gt;Etag: W/&amp;quot;1233&amp;quot;&lt;/code&gt;（weak validator），因為 &lt;code&gt;If-Match&lt;/code&gt; 標頭要求使用強比較函式&lt;sup id="fnref1:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;（即 &lt;code&gt;E/1&lt;/code&gt; ≠ &lt;code&gt;E/1&lt;/code&gt;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;），因此用戶端無法使用這個值來建立 &lt;code&gt;If-Match&lt;/code&gt; 條件式請求&lt;sup id="fnref2:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;。因此，如果伺服器傳送的是 &lt;code&gt;Etag: W/&amp;quot;1233&amp;quot;&lt;/code&gt;，則不應該在 &lt;code&gt;If-Match&lt;/code&gt; 中傳送任何值來進行條件式更新。&lt;/p&gt;
&lt;p&gt;如果要做快取的話，直接記錄 ETag，日後用 &lt;code&gt;If-None-Match&lt;/code&gt; header 讓伺服器比較版本就行&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;。通常來說 &lt;code&gt;If-Match&lt;/code&gt; 都是防止狀態改變的方法（POST 這些）&lt;sup id="fnref3:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;，CDN 通常不會去快取 POST 這類主要做 mutation 的方法，也就沒必要把 ETag 改成 weak representation，也就不太會遇到這個問題。&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;RFC 7232 – Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests – If-Match: &lt;a href="https://datatracker.ietf.org/doc/html/rfc7232#autoid-15"&gt;https://datatracker.ietf.org/doc/html/rfc7232#autoid-15&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref3:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;RFC 7232 – Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests – ETag Comparsion: &lt;a href="https://datatracker.ietf.org/doc/html/rfc7232#autoid-11"&gt;https://datatracker.ietf.org/doc/html/rfc7232#autoid-11&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>聊聊微服務是什麼 – 服務通訊</title><link>https://blog.pan93.com/posts/microservice-service-communication/</link><pubDate>Fri, 05 Sep 2025 20:22:00 +0800</pubDate><guid>https://blog.pan93.com/posts/microservice-service-communication/</guid><description>&lt;p&gt;這個 Tag 的第六個主題是「服務通訊」，講白話些就是「我要怎麼呼叫另一個服務的功能？」我們在第三個主題講了「服務通訊的大致邏輯」，以及在第四個和第五個主題花了很大的篇幅講了「服務怎麼 &lt;strong&gt;連線&lt;/strong&gt; 到服務」，但好像從來都沒有真正說「服務怎麼和服務 &lt;strong&gt;互動&lt;/strong&gt;」、「RPC 是什麼」，以及具體要怎麼通訊、有哪些通訊方法，以及有哪些通訊手段。這裡就要開始介紹了！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這篇是之前 SITCON 2025 的草稿，不是後面寫的，也還沒有做過 peer reviewing！如果有任何問題的話，也歡迎到 Threads 或 X 上標我指正。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="通訊是什麼"&gt;「通訊」是什麼？&lt;/h2&gt;
&lt;p&gt;在單體服務的世界，你要呼叫一個方法，比如「選課」，你通常是用函式或 HTTP API 呼叫完成的。舉例來說：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;你的前端往你的 API 發出了 &lt;code&gt;POST /api/v1/course/:course/selection&lt;/code&gt;。我們會稱這個網址叫做端點 (endpoint)。&lt;/li&gt;
&lt;li&gt;你的 API 有一個「選課」Controller，裡面有一個處理選課端點的「方法」(method)，這裡叫做 &lt;code&gt;SelectCourse&lt;/code&gt; 吧！&lt;/li&gt;
&lt;li&gt;接著你的 &lt;code&gt;SelectCourse&lt;/code&gt; 方法會透過呼叫 &lt;code&gt;GetStudentInfo&lt;/code&gt; 方法來取得這個學生的資訊，以及透過 &lt;code&gt;GetCourseContraints&lt;/code&gt; 方法取得課程的限修資訊&lt;/li&gt;
&lt;li&gt;接著 &lt;code&gt;SelectCourse&lt;/code&gt; 方法會根據這些資訊決定允不允許學生選這堂課，然後將選課結果插入資料庫當中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="monolithic service communication" loading="lazy" src="https://assets.blog.pan93.com/microservice-service-communication/01-monolithic-service-communication-optimised.avif"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📚 小提醒：在 MVCS 架構下，我們通常會叫這種功能性質的 class 為 &lt;strong&gt;Service&lt;/strong&gt;。不過為了防止跟微服務的 Service 混淆（雖然其實概念是一樣的），這裡就不會用 Service 這個名詞。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果你能理解上面這張圖，那我們把它換成微服務，你就很清楚我們要解決什麼「通訊」問題了：&lt;/p&gt;
&lt;p&gt;&lt;img alt="microservice communication" loading="lazy" src="https://assets.blog.pan93.com/microservice-service-communication/02-microservice-communication-optimised.avif"&gt;&lt;/p&gt;
&lt;p&gt;上面這張圖的青色線段，就是我們這次要探討的問題了：&lt;strong&gt;這些服務都不在一起，我們要怎麼呼叫對方的方法&lt;/strong&gt;——也就是「青色」這個線條，究竟怎麼實作？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🫨 選課服務是怎麼連線到使用者服務的？你可以往回看看「服務探索」這個章節！從這裡開始，我們都假定我們已經知道這些服務的 IP 和連線方式了，要處理的只是應用層的溝通問題。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="用-http-設計自己的-rpc"&gt;用 HTTP 設計自己的 RPC&lt;/h2&gt;
&lt;p&gt;首先，最直覺的方法，就是你給每個服務開一個 HTTP 的 API，用 HTTP 的呼叫邏輯和其他服務溝通。因為這些 HTTP 方法只是內部使用的，你不用像 REST API 一樣，需要認真思考每個端點的命名，只需要讓每個服務看得懂就好。因此，你這樣設計你的微服務溝通方法：&lt;/p&gt;</description></item><item><title>聊聊微服務是什麼 – 擴縮與負載平衡</title><link>https://blog.pan93.com/posts/microservice-scaling-and-lb/</link><pubDate>Fri, 05 Sep 2025 20:10:00 +0800</pubDate><guid>https://blog.pan93.com/posts/microservice-scaling-and-lb/</guid><description>&lt;p&gt;這個 Tag 的第五個主題是水平擴縮跟負載平衡。還記得我們昨天提到的「服務探索」嗎？到目前來講，我們只「探索」了一個服務，但是微服務的其中一個目標，就是在不擴展機器的情況下，盡可能把請求負載分擔到各個服務上。我們要怎麼做到這個「分擔」呢？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這篇是之前 SITCON 2025 的草稿，不是後面寫的，也還沒有做過 peer reviewing！如果有任何問題的話，也歡迎到 Threads 或 X 上標我指正。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="什麼是擴縮和負載平衡"&gt;什麼是擴縮和負載平衡？&lt;/h2&gt;
&lt;p&gt;答案意外得簡單：我們不就多開幾個服務，然後按一套規則平均把請求分擔到這些服務就好了嗎？恭喜你發明了水平擴縮 (horizontal scaling) 跟負載平衡 (load balancing) 了！這兩件事情的邏輯很簡單：「&lt;strong&gt;我們要在不動單一個節點的情況下，盡量把服務『擴縮』到各個地方，並且平均地『分配』請求到這些服務上？&lt;/strong&gt;」&lt;/p&gt;
&lt;p&gt;&lt;img alt="simple scaling" loading="lazy" src="https://assets.blog.pan93.com/microservice-scaling-and-lb/01-simple-scaling-optimised.avif"&gt;&lt;/p&gt;
&lt;p&gt;只是在實務上會更複雜，舉例來說，你會有 N 個 Gateway，連線到 M 個選課服務，這 M 個選課服務還會分別連線到 X 個課程服務，以及 Y 個使用者服務。我們接下來要解決的議題，就是如何在更複雜的場景，可以將連線分配到服務上、如何在單一服務下線時讓其他服務繼續工作，以及 HTTP 的世界，負載平衡通常該怎麼做。&lt;/p&gt;
&lt;h2 id="要怎麼擴縮服務"&gt;要怎麼擴縮服務？&lt;/h2&gt;
&lt;p&gt;首先，我們先從比較簡單的問題開始：「&lt;strong&gt;怎麼擴縮&lt;/strong&gt;？」它本質上就是多開幾個服務，你在好幾個終端機視窗，重複啟動你的服務，其實就是一種「擴展」(scaling up)；你把過剩的服務用 Ctrl-C 殺掉，其實就是一種「縮減」（scaling down）。不過你假如真的這樣做，會發現到「重複用相同的參數啟動服務會遇到 port 衝突的問題」（比如說不能有兩個服務開在 3000 port 上），所以你就需要將另一個服務開在不同的 port 上（比如說，3001 port），這樣一來，你就起了有兩個 replicas 的服務了！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📚 「Replicas」是什麼？其實就是「重複的實例」，比如說上圖中的 course-service（課程服務），我們可以稱他有 6 個 Replicas。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="要怎麼分配請求負載平衡"&gt;要怎麼分配請求（負載平衡）？&lt;/h2&gt;
&lt;p&gt;下一個問題，就是「&lt;strong&gt;怎麼分配請求&lt;/strong&gt;？」這個問題跟「服務探索」一樣，可以從用戶端和伺服器端下手。就以上面的圖來說：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用戶端解法：Gateway 在知道 6 個課程服務的位址之後，用分配演算法（隨機挑選、循環調度 (round robin)，或者是比較複雜的根據負載分配）來決定要連線到哪台機器。&lt;/li&gt;
&lt;li&gt;伺服器端解法：我們可以在前面掛一個代理伺服器（用 NGINX、Caddy），由代理伺服器透過分配演算法來決定要代理到哪台機器，而 Gateway 直接連線到這個代理伺服器就好。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中，「伺服器端解法」中那個「代理伺服器」的別稱就是「&lt;strong&gt;負載分配器&lt;/strong&gt;」（Load Balancer）。你應該在 AWS、GCP、Cloudflare 等 IaaS 上看過這個名詞，實際上他的原理就是透過一套演算法，將請求分配、代理到指定的服務。&lt;/p&gt;</description></item><item><title>聊聊微服務是什麼 – 服務探索</title><link>https://blog.pan93.com/posts/microservice-service-discovery/</link><pubDate>Thu, 06 Feb 2025 23:34:00 +0800</pubDate><guid>https://blog.pan93.com/posts/microservice-service-discovery/</guid><description>&lt;p&gt;今年我 SITCON 有投一篇微服務的議程：&lt;a href="https://sitcon.org/2025/agenda/a03517/"&gt;「選課卡成狗？微服務架構帶你翻轉校園系統」&lt;/a&gt;。在 SITCON 之前，我打算每天在 Blog 上寫一篇和 Cloud Native 相關的短文，來當作議程的前導內容。當然針對每一篇短文的意見回饋（看不懂也是一種意見反饋 🥺），最終都有助於我產出更好的議程內容～&lt;/p&gt;
&lt;p&gt;這個 Tag 的第四個主題是「服務探索」。從這個主題開始，我們要開始探討前三章說的微服務架構會有什麼問題，以及可以怎麼克服了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;考慮到 Blog 本身不是一個很好的互動平台，我在每篇文章的底下都會留「&lt;strong&gt;💬 互動區塊&lt;/strong&gt;」，連結到和這篇文章相關的社交媒體上。你可以在社交媒體上和這篇文章互動～&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="同台機器上服務如何互相連線"&gt;同台機器上，服務如何互相連線？&lt;/h2&gt;
&lt;p&gt;前文的結尾我有留一個問題：「如何讓服務之間可以互相連線？」你或許會覺得這問題非常基本，因此我們不妨拿之前的微服務架構模擬看看。假如你在本機端而且不想配置網路的話，最直接的做法可能是每個服務都開不同的 port，讓 Gateway 直接用 port 連線。比如說你把使用者服務安排在 port 3001、把選課服務安排在 port 3002，然後 Gateway 用 &lt;code&gt;localhost:3001&lt;/code&gt; 連線使用者服務、用 &lt;code&gt;localhost:3002&lt;/code&gt; 連線選課服務；selection-service 也是用 &lt;code&gt;localhost:3001&lt;/code&gt; 連線使用者服務、用 &lt;code&gt;localhost:3002&lt;/code&gt; 連線選課服務。&lt;/p&gt;
&lt;p&gt;&lt;img alt="connecting services with ports" loading="lazy" src="https://assets.blog.pan93.com/microservice-service-discovery/connecting-services-with-ports.png"&gt;&lt;/p&gt;
&lt;p&gt;事實上假如你只在一台機器上運作的話，這種方法真的沒有什麼問題，甚至可以說是行之有年了。&lt;/p&gt;
&lt;h2 id="多台機器上服務如何互相連線"&gt;多台機器上，服務如何互相連線？&lt;/h2&gt;
&lt;p&gt;不過假如我們進入到分散式的世界：把這堆服務打散到各個機器上，這樣的做法會碰到什麼問題呢？我們在第一章有分析出選課相關的功能相對耗資源，所以我們把「選課系統」獨立到不同的機器（這裡叫他機器 2）上。首先不同機器肯定會有不同的 IP，所以「機器 1」要連線到「選課服務」，肯定就不能用 &lt;code&gt;localhost&lt;/code&gt; 連線了，我們肯定是要用「機器 2」的 IP 搭配上之前設定的 port 進行連線，也就是下面這張圖。&lt;/p&gt;
&lt;p&gt;&lt;img alt="connect services with ports in different machines" loading="lazy" src="https://assets.blog.pan93.com/microservice-service-discovery/connecting-services-with-ports-in-different-machines.png"&gt;&lt;/p&gt;
&lt;p&gt;問題來了：IP 是永恆不變的嗎？機器的 IP 可能會因為區域變動（比如從新北機房移到高雄機房）而變化，我們也不一定能保證「選課服務」一定只在機器 2 上面。甚至說假如其他服務也開始有壓力，我們想要把它分出機器 1，我們也同樣需要關心到 IP 地址的議題。既然 IP 不是永恆不變的，我們就要關心起「如果 IP 改動時，會發生什麼樣的事情。」試想一下，我們把「選課服務」的 IP 換掉，我們需要改掉多少個服務連線位址呢？&lt;/p&gt;</description></item><item><title>聊聊微服務是什麼 – 閘道和服務通訊基礎</title><link>https://blog.pan93.com/posts/microservice-gateway-and-communication/</link><pubDate>Wed, 05 Feb 2025 22:29:00 +0800</pubDate><guid>https://blog.pan93.com/posts/microservice-gateway-and-communication/</guid><description>&lt;p&gt;今年我 SITCON 有投一篇微服務的議程：&lt;a href="https://sitcon.org/2025/agenda/a03517/"&gt;「選課卡成狗？微服務架構帶你翻轉校園系統」&lt;/a&gt;。在 SITCON 之前，我打算每天在 Blog 上寫一篇和 Cloud Native 相關的短文，來當作議程的前導內容。當然針對每一篇短文的意見回饋（看不懂也是一種意見反饋 🥺），最終都有助於我產出更好的議程內容～&lt;/p&gt;
&lt;p&gt;這個 Tag 的第三個主題，就來補充我們昨天微服務中一直沒有提到的部分：「Gateway」吧！我們昨天聊了服務的拆法（領域拆分），以及 RPC 分查詢和更動的目的（CQRS），但是你會發現到有個服務長得和其他服務不太一樣：Gateway。Gateway 沒有 RPC 方法，而且是唯一一個連接到前端的服務。為什麼需要 Gateway，以及 Gateway 是怎麼和其他服務通訊的呢？這篇文章就來詳細解釋這個問題。最後，我們也會簡單說明微服務之間的通訊流程。&lt;/p&gt;
&lt;p&gt;&lt;img alt="current microservice design" loading="lazy" src="https://assets.blog.pan93.com/microservice-gateway-and-communication/current-microservice.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;考慮到 Blog 本身不是一個很好的互動平台，我在每篇文章的底下都會留「&lt;strong&gt;💬 互動區塊&lt;/strong&gt;」，連結到和這篇文章相關的社交媒體上。你可以在社交媒體上和這篇文章互動～&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="什麼是-gateway"&gt;什麼是 Gateway？&lt;/h2&gt;
&lt;p&gt;「Gateway」（閘道）， 基本上就是你那些微服務的前台，負責給前端一組好用、封裝自 RPC 的 REST 或 GraphQL API，同時在轉送給微服務前進行正確的權限檢查（比如說「加入課程」只有管理員能執行）。這樣一來，前端就不需要知道所有微服務的位置、了解服務切分的細節，只需要和 Gateway 進行互動就好。&lt;/p&gt;
&lt;h2 id="拆出-gateway-的意義"&gt;拆出 Gateway 的意義&lt;/h2&gt;
&lt;p&gt;你或許會疑惑「為什麼我們不要一步到位，讓前端直接連線這些微服務呢？」首先我們先看看讓前端連線微服務會發生什麼事情：&lt;/p&gt;
&lt;p&gt;&lt;img alt="microservices without gateway" loading="lazy" src="https://assets.blog.pan93.com/microservice-gateway-and-communication/services-without-gateway.png"&gt;&lt;/p&gt;
&lt;p&gt;現在前端會直接連線到這些微服務上。假如沒有 Gateway，如你所見，「認證」和「授權」的重責大任就落在每個微服務身上了。身分認證的目的是判斷請求者持有的 token 是否有效，確定是對應使用者登入的；授權的目的是檢查對應使用者是否可以取用這個方法，防止使用者進行越權的操作。&lt;/p&gt;
&lt;p&gt;因為每個微服務都需要做一次認證和授權，單個微服務會需要處理自身領域外的事情，增加複雜度&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;：首先，每個微服務都需要有自己一套進行認證和授權的程式碼，除了拖慢呼叫一個 RPC 方法的速度（現代微服務一個 Endpoint 可能會需要呼叫好幾十個 RPC 方法），微服務本身也會因為這些驗證而變得臃腫。接著，認證和授權都需要依賴「使用者服務」，如果「使用者服務」故障了，幾乎所有微服務都會停擺。最後，服務之間的通訊該怎麼認證呢？如果你的認證方式是 OAuth 的話，你很需要給所有服務組合產生一組 &lt;a href="https://auth0.com/blog/using-m2m-authorization/"&gt;M2M token&lt;/a&gt;⋯⋯&lt;/p&gt;
&lt;p&gt;除了後端一團糟外，前端也會一團亂。你的前端需要先知道「我需要的方法在哪個服務上」，然後還需要知道這個服務的連線網址。你的 API 呼叫時需要指向各種不同的網址，當你要撤換服務時，需要一一檢查前端有沒有地方引用到這個服務，增加你撤換和更新這些服務時的困難度。同時你的這些服務因為都跟使用者的前端（也就是用戶端）扯上關係了，所以設計上又需要考慮用戶端會怎麼搞壞你的服務，並且需要花上更多時間把你的 RPC 方法封裝得更適合讓任意用戶端呼叫（比如因此捨棄簡單的 gRPC，轉為使用 REST API，去提升各個用戶端的相容性）。&lt;/p&gt;</description></item><item><title>聊聊微服務是什麼 – 入門微服務</title><link>https://blog.pan93.com/posts/microservice-introducing-microservice/</link><pubDate>Tue, 04 Feb 2025 23:44:00 +0800</pubDate><guid>https://blog.pan93.com/posts/microservice-introducing-microservice/</guid><description>&lt;p&gt;今年我 SITCON 有投一篇微服務的議程：&lt;a href="https://sitcon.org/2025/agenda/a03517/"&gt;「選課卡成狗？微服務架構帶你翻轉校園系統」&lt;/a&gt;。在 SITCON 之前，我打算每天在 Blog 上寫一篇和 Cloud Native 相關的短文，來當作議程的前導內容。當然針對每一篇短文的意見回饋（看不懂也是一種意見反饋 🥺），最終都有助於我產出更好的議程內容～&lt;/p&gt;
&lt;p&gt;這個系列的第二個主題，還是延續我們上次的選課系統設計。昨天我們發現到單體的方式開始造成資源的浪費以及資料庫的瓶頸，所以我們或許可以看看怎麼把選課系統拆成雲端原生的微服務架構。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;考慮到 Blog 本身不是一個很好的互動平台，我在每篇文章的底下都會留「&lt;strong&gt;💬 互動區塊&lt;/strong&gt;」，連結到和這篇文章相關的社交媒體上。你可以在社交媒體上和這篇文章互動～&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Monolithic System" loading="lazy" src="https://assets.blog.pan93.com/microservice-introducing-microservice/current-monolithic-system.png"&gt;&lt;/p&gt;
&lt;h2 id="怎麼將單體服務拆分成微服務"&gt;怎麼將單體服務拆分成微服務？&lt;/h2&gt;
&lt;p&gt;或許你會疑惑「微服務」是什麼。首先就「服務」這個詞來說，後端是個服務，前端也是個服務，基本上任何能提供 API 進行呼叫的都是服務。「微服務」本質上就是把我們的後端拆得更細緻：比如說我們可以把使用者相關的東西變成一個服務、把課程相關的東西變成一個服務、把志願序變成一個服務等等。&lt;/p&gt;
&lt;p&gt;&lt;img alt="first microservice" loading="lazy" src="https://assets.blog.pan93.com/microservice-introducing-microservice/first-microservice.png"&gt;&lt;/p&gt;
&lt;p&gt;我們通常會把每個服務管轄的部分稱之為「&lt;strong&gt;領域&lt;/strong&gt;」，只有這個區域內的服務可以直接操縱資料庫的資料，而不在這個區域的只能透過 RPC（遠端程序呼叫，比面向用戶端的 REST API 相對隨便，主要供內部使用的 API）撈資料。你可能會想「領域是不是就是資料表的名稱？」，但實際上其實不止於此，粒度通常會大得多，通常會追求「服務（的領域）可以獨立運作」。我們把以「領域」進行思考的一套模式叫做 DDD（領域驅動開發），實際展開的篇幅會相當長，這裡就不對拆服務和 DDD 多做說明（實際上你也不一定要學會 DDD 才能用微服務，靠經驗拆也是可以的！）&lt;/p&gt;
&lt;p&gt;就以上面的例子來說，我自己認為使用者服務（user-service）、課程資訊服務（course-service）和選課服務（selection-service）是三個領域上可以獨立運作的服務——換言之，假如使用者服務掛掉，我預期我還能看課程資訊以及改動志願序；假如選課服務掛掉，我一樣可以更新我的選修學分。不過你應該也有發現到「選課服務」相對複雜一點，因為插入志願和最終志願序需要做很多判斷邏輯，需要連線到使用者服務看權重，以及連線到課程服務查詢限修規則，這時我們可能需要處理選課服務在其他兩個服務掛掉時的處理邏輯。但撇除掉這兩個 RPC 方法，服務大體上還是可以獨立運作的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;【🤔 想想看！】你覺得上面的微服務架構，是不是一個好的拆法？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="微服務的水平擴縮-scaling"&gt;微服務的水平擴縮 (scaling)&lt;/h2&gt;
&lt;p&gt;接著回答昨天的問題：我們這樣開發出的服務，可以怎麼降低資料庫的壓力，以及怎麼針對性的對服務進行擴充來分散壓力？首先，從上圖其實就能很明顯看到「我們把每個領域對應的資料庫都拆出去了，」換句話說，每個資料庫只需要負責自己領域內的事情（使用者資訊、課程資訊、選課資訊），壓力自然就比單體的「包山包海」小上很多。接著，其實每個微服務都應該可以水平擴縮 (scaling)，所以你可以規劃「志願序服務的壓力比較大，所以我們可以開多一點服務來平衡」；「使用者服務幾乎不怎麼需要讀取，所以我們可以就開少少的機器就好」：&lt;/p&gt;
&lt;p&gt;&lt;img alt="microservice horizontal scaling" loading="lazy" src="https://assets.blog.pan93.com/microservice-introducing-microservice/microservice-horizontal-scaling.png"&gt;&lt;/p&gt;
&lt;p&gt;接下來我們也會講到很多微服務上的設計技巧，來發揮微服務更大的作用——比如快取。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;【🤔 想想看！】我們要怎麼把請求分散到上面說的這三個微服務呢？是不是所有微服務都必須共用一套狀態（資料庫）？共用的缺點是什麼，以及快取區需要共用嗎？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="查詢與更動分離-cqrs"&gt;查詢與更動分離 (CQRS)&lt;/h2&gt;
&lt;p&gt;你接著還會疑惑「查詢動作跟更動動作」差在哪裡。舉個例子，為什麼我們沒有選擇加一個「插入志願之後列出學生志願序」的查詢 + 更動 RPC，而是選擇分開變成「插入志願 RPC」+「志願序 RPC」呢？其實這種叫 CQRS，把「查詢」跟「更動（命令）」分開的一種設計模式。&lt;/p&gt;
&lt;p&gt;為什麼我們要分開查詢（讀取）跟更動（寫入）呢？通常讀取動作不用擔心狀態問題，也就是說「就算我今天讀取 1000 次同一筆資料，只要沒有改動，得到的結果應該都要是一樣的」；寫入操作的狀態問題就複雜得多，執行順序、重複插入就足以造成大影響。所以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;讀取動作我們可以快取、可以在唯讀的資料庫 replica 實例讀取（對，很多資料庫系統是可以建立出很多個跟隨主要資料庫的唯讀 replica 實例的，通常我們叫這種功能為複寫 – replication）&lt;/li&gt;
&lt;li&gt;寫入操作只能在主要資料庫進行操作，並且通常是不能快取的，相對來講效能改進的彈性會小一些。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在我們知道讀取和寫入兩個的複雜度不同後，CQRS 的設計就變得合理多了。就以「課程資料庫」來說，我們可以這樣規劃查詢和寫入：&lt;/p&gt;</description></item><item><title>聊聊微服務是什麼 – 從單體服務進行擴縮</title><link>https://blog.pan93.com/posts/microservice-monolithic-scaling/</link><pubDate>Tue, 04 Feb 2025 23:26:34 +0800</pubDate><guid>https://blog.pan93.com/posts/microservice-monolithic-scaling/</guid><description>&lt;p&gt;今年我 SITCON 有投一篇微服務的議程：&lt;a href="https://sitcon.org/2025/agenda/a03517/"&gt;「選課卡成狗？微服務架構帶你翻轉校園系統」&lt;/a&gt;。在 SITCON 之前，我打算每天在 Blog 上寫一篇和 Cloud Native 相關的短文，來當作議程的前導內容。當然針對每一篇短文的意見回饋（看不懂也是一種意見反饋 🥺），最終都有助於我產出更好的議程內容～&lt;/p&gt;
&lt;p&gt;這個系列的第一個主題，就先從微服務這個主軸開始吧！但在這之前我們或許可以先聊聊「&lt;strong&gt;單體架構可能會遇到什麼樣的瓶頸&lt;/strong&gt;」，以及我們要怎麼使用一些技術來稍微改善你的單體系統。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;考慮到 Blog 本身不是一個很好的互動平台，我在每篇文章的底下都會留「&lt;strong&gt;💬 互動區塊&lt;/strong&gt;」，連結到和這篇文章相關的社交媒體上。你可以在社交媒體上和這篇文章互動～&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="單體服務的瓶頸"&gt;單體服務的瓶頸&lt;/h2&gt;
&lt;p&gt;假設你今天要開發一套用比序來選課的系統，你或許會像下圖這樣來設計你的系統。其中後端就是一個很大的應用程式，前端去呼叫後端來選課。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Monolithic System" loading="lazy" src="https://assets.blog.pan93.com/microservice-monolithic-scaling/monolithic-system.png"&gt;&lt;/p&gt;
&lt;p&gt;乍看之下是個挺合理的設計吧？但我們設想一種情況：假如現在已經到了選課的尾聲，大家都會想要看看「自己選的課是否可以上得了」，所以「選課人數」Endpoint 的請求量也會隨時增加，然後系統的 CPU 資源就被「選課人數 Endpoint」吃光了。因為登入、選課、課程名單、志願序等等的 Endpoint 也在同個系統上，所以你的選課系統除了前端的部分會全部掛掉，學生們準備在 Dcard 上罵爆你的系統了。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Monolithic on High Loading" loading="lazy" src="https://assets.blog.pan93.com/microservice-monolithic-scaling/monolithic-on-high-load.png"&gt;&lt;/p&gt;
&lt;h2 id="擴縮單體服務"&gt;擴縮單體服務&lt;/h2&gt;
&lt;p&gt;此時你會想到：那我們多開好幾套單體的後端 (replicas)，然後前端隨機選擇 API（也就是所謂的「負載平衡」）呢？其實確實是個可行的方案，不過你首先要讓你的後端變成無狀態的 (Stateless) 的。「無狀態」是什麼概念呢？就以下圖來說，我們無論選到哪個 instance 的 Endpoint，呼叫結果都應該要是一致的。換言之，你的後端不可以儲存只有這個 instance 知道的東西，也就是所謂的「狀態」。當然取決於你的設計，&lt;strong&gt;你可能多少會不小心存一些狀態在後端裡面（比如登入的 session 以及 lock 檔案⋯⋯）&lt;/strong&gt;，所以你可能會需要花點時間重構這些邏輯，讓這堆狀態不要跟後端放在一起（或甚至變成不用儲存狀態也能判斷的東西，比如 JWT）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;【🤔 想想看！】哪些東西可能會導致一個服務變成有狀態的（Stateful）？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Load balancing for Monolithic System" loading="lazy" src="https://assets.blog.pan93.com/microservice-monolithic-scaling/load-balancing-for-monolithic-system.png"&gt;&lt;/p&gt;
&lt;h2 id="單體服務的擴縮問題"&gt;單體服務的擴縮問題&lt;/h2&gt;
&lt;p&gt;在完成相關的重構後，就算其中一個 instance 有著很大的負載，其他 instance 也能有效的分散掉請求，讓系統不至於完全停擺。不過這種方法粒度或許還是太大了——我們只有 1 個 endpoint 遇到瓶頸，但卻需要因此開出 5 個（甚至更多）完整的後端 instances，資源用量上會不會變得太多；而且所有 instances 最終還是連到一台資料庫上，遇到大量讀取、寫入的場景可能還是會 lag。如果我們用微服務、分散式系統的邏輯重新規劃後端，我們有沒有機會解決掉這個問題？&lt;/p&gt;</description></item><item><title>把 Python 的 venv 移到其他機器</title><link>https://blog.pan93.com/posts/move-venv-to-other-machines/</link><pubDate>Mon, 25 Nov 2024 13:14:26 +0800</pubDate><guid>https://blog.pan93.com/posts/move-venv-to-other-machines/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;要怎麼在不重新下載東西的情況下，把一整包 code 包含依賴本身，移到新的地方呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;舉個之前遇到的例子：我想在學校部署一個用 Python Streamlit 框架寫的程式碼評測工具，但是在測試部署時發現一個非常頭痛的問題：學校的連外網路特別慢，導致 pip 幾乎無法正常完成安裝。在嘗試了 &lt;code&gt;pip download&lt;/code&gt; 以及一些打包方案後，發現 Streamlit 的執行最終都會缺幾個元件導致啟動失敗，最終都還是需要用 &lt;code&gt;pip install&lt;/code&gt; 補全依賴。最後我想到一個 workaround：拿其他同學的 Windows 筆電先把程式準備好，再把準備好的程式複製到新的地方。&lt;/p&gt;
&lt;p&gt;&lt;img alt="The obstacle when installing with Pip" loading="lazy" src="https://assets.blog.pan93.com/move-venv-to-other-machines/pip-install-obstacle.png"&gt;&lt;/p&gt;
&lt;p&gt;不過要怎麼「準備好程式」然後「複製到新的地方」呢？&lt;/p&gt;
&lt;p&gt;Python 不像 Go 和 Rust 可以編譯成靜態的執行檔，也不像 Node.js 和 PHP 有著各個專案獨立的 &lt;code&gt;node_modules&lt;/code&gt; 或 &lt;code&gt;vendor&lt;/code&gt; 資料夾，可以搬到其他地方而保持程式的依賴正常運作。不過 Python 有個很類似 &lt;code&gt;node_modules&lt;/code&gt; 的東西——Virtualenv，搞不好我們真的能像 &lt;code&gt;node_modules&lt;/code&gt; 一樣直接把整組專案複製到其他電腦上，專案就能跑了。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Concept about making venv as an application bundle" loading="lazy" src="https://assets.blog.pan93.com/move-venv-to-other-machines/venv-bundle-concepts.png"&gt;&lt;/p&gt;
&lt;p&gt;但是 Stack Overflow 的文章又提到「venv 通常不能直接複製到其他電腦上」。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In general you can&amp;rsquo;t copy virtual environments anywhere, Docker or otherwise.&lt;/strong&gt; They tend to be tied to a very specific filesystem path and a pretty specific Python installation. If you knew you had the exact same Python binary, and you copied it to the exact same filesystem path, you could probably COPY it in as-is, but the build system would be extremely fragile. &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;</description></item><item><title>搭建一個有圖床和統計功能的 Blog</title><link>https://blog.pan93.com/posts/create-a-blog-with-zeabur/</link><pubDate>Sun, 24 Nov 2024 00:56:03 +0800</pubDate><guid>https://blog.pan93.com/posts/create-a-blog-with-zeabur/</guid><description>&lt;h2 id="tldr"&gt;tl;dr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;使用 Zeabur 部署 Blog&lt;/li&gt;
&lt;li&gt;使用 Cloudflare R2 當圖床，Cloudreve 管理&lt;/li&gt;
&lt;li&gt;使用 Umami 進行網站資料統計&lt;/li&gt;
&lt;li&gt;選擇性部署 CodiMD / Hedgedoc 方便行動裝置編輯&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="常見的方案有什麼問題"&gt;常見的方案有什麼問題&lt;/h2&gt;
&lt;p&gt;純粹的 GitHub Pages 架設靜態 Blog，搭配 Google Analytics（或 Cloudflare Web Analytics）雖然是最便宜的選擇，但對於媒體管理上和草稿編輯還是不太方便。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;統計工具方面&lt;/strong&gt;，除了 Google Analytics 之外，幾乎沒有什麼功能特別強大，可以看單一使用者流向的統計工具。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;圖床方面&lt;/strong&gt;，媒體放在 GitHub repository 上會造成 repo 迅速膨脹，但又擔心其他免費圖床如 Imgur 會倒閉。而且 Imgur 不能上傳圖片以外的資源！🥺&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;草稿編輯方面&lt;/strong&gt;，GitHub 沒有提供一個比較好的 Markdown 書寫工具，要獲得好的書寫體驗，就需要在電腦上用 VS Code 等等的 Markdown editor 書寫，局限了在手機上完成草稿或者編輯文章的能力。&lt;/p&gt;
&lt;h2 id="怎麼解決"&gt;怎麼解決？&lt;/h2&gt;
&lt;p&gt;作為一個 self-hosted 跟偏好 Cloud Native 方案的使用者，我會這麼規劃我的 blog：&lt;/p&gt;
&lt;p&gt;&lt;img alt="structure" loading="lazy" src="https://assets.blog.pan93.com/create-a-blog-with-zeabur/structure.png" title="Structure"&gt;&lt;/p&gt;
&lt;p&gt;這次我使用的 Blog SSG 是 &lt;strong&gt;Hugo&lt;/strong&gt;，速度確實快，而且 template 比 Hexo 簡潔和清晰一點。&lt;/p&gt;</description></item><item><title>重新整理 Pan.blog……</title><link>https://blog.pan93.com/posts/new-blog/</link><pubDate>Fri, 22 Nov 2024 22:27:38 +0800</pubDate><guid>https://blog.pan93.com/posts/new-blog/</guid><description>&lt;p&gt;把 pan.blog 重新翻新了一下，主要是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;換成 Hugo，提升編譯速度&lt;/li&gt;
&lt;li&gt;圖床放到 CDN 上，不跟 GitHub 放在一起了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其實 &lt;code&gt;pan.blog&lt;/code&gt; 我前一陣子就想重新整理了，只是一直沒有動力來做。
不過 ChatGPT 開始支援 Web Search，讓我覺得「把文章寫在 Google 可以索引的地方，之後也能幫到自己」，
再加上 X 的走向讓人不太樂觀，所以我打算把一些有價值的東西用 Blog 的方式留存。&lt;/p&gt;
&lt;p&gt;這個新 Blog 有把一些我覺得值得留存的東西從老的 Hexo 站移過來，之後也會寫一些技術短篇～&lt;/p&gt;</description></item><item><title>特殊選才踩雷心得</title><link>https://blog.pan93.com/posts/srecruit-opinions/</link><pubDate>Fri, 14 Apr 2023 02:23:00 +0800</pubDate><guid>https://blog.pan93.com/posts/srecruit-opinions/</guid><description>&lt;blockquote&gt;
&lt;p&gt;打擾了各位，想問一下如果想要透過特殊選材的話，有大量的競賽經驗/表現，還是有實作成品，哪個會比較好？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我當年特選是以實作為重的。我踩過坑了，所以我能告訴你 &lt;strong&gt;不要想著你的實作可以獲得教授青睞&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;通常在高中年紀做的作品——比如自己寫的內核、寫的網站服務、寫的系統，&lt;strong&gt;都是垃圾&lt;/strong&gt;。教授早已閱歷無數，你引以為傲的作品，可能對他們來說就是 &lt;code&gt;print(&amp;quot;Hello, World&amp;quot;)&lt;/code&gt; 而已。除非你的作品真的很知名——就比如說你造出了一個每個月使用人次高達 10 萬的東西，要不然說實話，你只是證明「你對這個領域有很淺薄的研究」——但一點都不特殊。當然，假如你能把你的作品做出相當大的特色（你造出了一個 &lt;code&gt;O(1)&lt;/code&gt; 的旅行者問題演算法），甚至你還因此發上期刊，這就另當別論。&lt;/p&gt;
&lt;p&gt;所以既然大家都是菜雞，那教授要怎麼選出「特殊」的？當然是量化下來的能力。不要看不起 APCS 還有一堆競賽：這個是你唯一可以拿來說嘴的了。你可以在這些競賽中大談你是如何把基礎能力用到極致，用到把其他競賽者打爆的——其實這樣也有一點「專案」的效果了，都是基礎能力應用，但至少教授可以知道「你的基礎能力確實超越同儕。」&lt;/p&gt;
&lt;p&gt;總結來說，不要以為你的專案用了些什麼「設計模式」、「物件導向」，就可以變成教授人人都想搶的人才——說實話，你的專案除非有名到教授想要知道，否則教授只會覺得你只是寫了一堆比較進階的 Hello, World。乖乖競賽，把你的能力用來打爆其他人，這才是特選成功的法則。&lt;/p&gt;
&lt;p&gt;當然你也可以繼續拿你的專案當作是特選的題材，但就警告你不要害到自己一所學校都沒上然後學測還爆掉。我有個開發了一堆專案、甚至還算知名，而且還經常參與各種社群活動的朋友——他的 4 間特選全部爆炸，學測失利，最後分科上了間沒有契合他興趣的科系。我自己幸好還有些比賽，&lt;a href="https://blog.pan93.com/posts/college-or-university/"&gt;所以勉強上了間普普通通的大學&lt;/a&gt;，但也是挺後悔自己當初叛逆沒去考 APCS。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;原文寫在《中學資訊討論群 CISC》：&lt;a href="https://discord.com/channels/755456963265036309/1078522742057480232/1096319706664804394"&gt;https://discord.com/channels/755456963265036309/1078522742057480232/1096319706664804394&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>從個人網站學習專案管理的方法論</title><link>https://blog.pan93.com/posts/produce-a-perfect-site/</link><pubDate>Fri, 16 Dec 2022 00:02:06 +0000</pubDate><guid>https://blog.pan93.com/posts/produce-a-perfect-site/</guid><description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;img alt="個人網站的截圖" loading="lazy" src="https://assets.blog.pan93.com/produce-a-perfect-site/preview.webp"&gt;&lt;/p&gt;
&lt;p&gt;最近在做一個有簡歷功能的個人網站。&lt;/p&gt;
&lt;p&gt;大學的網頁課程要上傳一個 styled 的 profile。起初兩週我想了許多種方案，比如「直接把之前超前進度的作業&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; 改成亮色系，然後繳出去」、「用 ChatGPT 幫我寫一份 styled 的網頁。」但我覺得都太敷衍，再加上當時有一些事情要處理，所以我就先放著——&lt;strong&gt;直到繳交前 3 天&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;繳交前夕，我突然有個打算：「&lt;strong&gt;要不要試著超前上課進度&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;？&lt;/strong&gt;」如果作業還繼續用 Vanilla JS 也實在太沒有新意，而且我這時突然想到一個很有意思的主意：做出一個可以分享給別人（也方便自我介紹）的簡歷。&lt;/p&gt;
&lt;p&gt;我面試的時候常常被問到「你的作品集在哪裡」、「你的簡歷在哪」。我是有個人形象頁面，但幾乎沒有說明自己的經歷：這也就導致沒有人——包括我自己——知曉我究竟參與了哪些專案、製作了哪些 projects，以及設計了什麼東西。如果能藉由這次作業的機會打造出一個可以分享給別人（也方便自我介紹）的簡歷，那肯定會讓這個作業更有價值。&lt;/p&gt;
&lt;p&gt;「做一個好看的簡歷網站」和「用現代前端工程標準製作專案」，便成為這個專案的兩大目的。&lt;/p&gt;
&lt;h2 id="滾動式開發逐步趨近完美"&gt;「滾動式開發」：逐步趨近完美&lt;/h2&gt;
&lt;p&gt;&lt;img alt="貢獻時長與程式碼列數" loading="lazy" src="https://assets.blog.pan93.com/produce-a-perfect-site/github-stat.webp"&gt;&lt;/p&gt;
&lt;p&gt;專案本身沒有用到傳統意義上的敏捷開發，但我在這個專案實踐了一些敏捷開發的「滾動式」精神。&lt;/p&gt;
&lt;p&gt;「滾動式」的概念其實就是「慢慢達成趨近完美」：應該要先專注在核心的部分（比如這次的專案就是以個人介紹為核心），然後再慢慢地把其他的東西（比如作品集、開源貢獻）加進去。重構也是：一開始不用追求到完全的 best practice——細節可以留待日後再來改善，&lt;strong&gt;但如果為了細節而導致核心部分的缺失，便是本末倒置&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="時間安排的重要性"&gt;時間安排的重要性&lt;/h2&gt;
&lt;p&gt;雖然這麼說，但我仍然想追求完美主義：我很討厭承認一個長得超醜、超難維護的 snippet 是我寫的，我希望可以帶給閱聽者極其完美的體驗。正是因為如此，這次的作業依然是花了相當多的時間重構。可是比起細節，內容的完善明顯比較重要——更何況我因為這個 project 荒廢了太多其他的事情，&lt;strong&gt;排除掉學習到的經驗，投入的時間成本其實早就超出了這個專案可以帶來的價值。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;**「時間安排」，在無論是在專案管理、還是日常生活的方方面面都十分重要。**安排任務的優先級、並且專注在完成最核心的事情，為專案的管理之道。我覺得我時間管理一直都做得不太好——想法太多，時間卻是有限的——是時候逼迫自己從無限的想法中切分，而不是讓一個想法佔據掉太多的時間。&lt;/p&gt;
&lt;h2 id="勇於發問明白自己的不足"&gt;勇於發問，明白自己的不足&lt;/h2&gt;
&lt;p&gt;&lt;img alt="在 Telegram 上詢問設計師細節問題" loading="lazy" src="https://assets.blog.pan93.com/produce-a-perfect-site/ask-designer-at-telegram.webp"&gt;&lt;/p&gt;
&lt;p&gt;寫網站的時候我就有和好幾位朋友共同研討「如何做出舒適的設計」，並且共同討論如何做出優秀的動效設計。這次我就和一位有著不小聲望的專業前端工程師和設計師，共同討論網頁每一處細節的設計。&lt;/p&gt;
&lt;p&gt;另外，網站在開發後期也有遇到以及在遇到效能問題時與幾位更專業的前端工程師共同查出問題。在網頁撰寫末尾，我就有遇到一個很嚴重的效能問題：滑到卡片的時候會嚴重掉幀，但 Profile 裡面並沒有指出具體是哪個函數導致問題。後來和朋友討論之後，鎖定是 shadow 動效導致的問題，而在解決之後，效能問題也就臨刃而解。&lt;/p&gt;
&lt;p&gt;&lt;img alt="請教其他前端工程師關於動效效能的問題" loading="lazy" src="https://assets.blog.pan93.com/produce-a-perfect-site/performance-question.webp"&gt;&lt;/p&gt;
&lt;h2 id="不要過度工程化-over-engineering"&gt;不要過度工程化 (over-engineering)&lt;/h2&gt;
&lt;p&gt;之前看過一篇文章，說：「Best Practice 是總結出來的，&lt;strong&gt;不代表一開始就得導入這些東西。&lt;/strong&gt;」工程更像是事情在遇到事情或需要擴充時的解決方法，而不是在沒必要的時候就無止境導入的玩意。&lt;/p&gt;
&lt;p&gt;另外，不完美是難免的：像是我的 codebase 就重構了數次，即將發起的一次是發現自己違背了 Tailwind CSS 的 Atomic CSS 理念，打算全數改回 Component 的形式。重構是個學習，不要害怕不完美——不完美才能學習，學習才能成長。&lt;/p&gt;
&lt;h2 id="目前-app-還會繼續改進的地方"&gt;目前 App 還會繼續改進的地方&lt;/h2&gt;
&lt;p&gt;除了上面提到的 bad smell 之外，我還打算加上一些更多 GitHub 的元素（比如個人的貢獻狀況、以及貢獻圖表）。不過把 bad smell 做完之後應該就是直接 promote 然後暫時放著了——有更多更重要的專案得做啊。&lt;/p&gt;</description></item><item><title>什麼時候該引入 Lockfile？</title><link>https://blog.pan93.com/posts/when-include-lockfile/</link><pubDate>Tue, 18 Oct 2022 13:37:00 +0800</pubDate><guid>https://blog.pan93.com/posts/when-include-lockfile/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://twitter.com/bystartw/status/1582244519319597056?s=61&amp;amp;t=E09UfEWbDsXiuks4rs0Y3g"&gt;原文撰於 Twitter&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="撰文初衷"&gt;撰文初衷&lt;/h2&gt;
&lt;p&gt;&lt;img alt="第一次看到沒有 commit package-lock.json 的 repo, posted by @as790726, on 2022/10/17." loading="lazy" src="https://assets.blog.pan93.com/when-include-lockfile/twitter-original-post.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;第一次看到沒有 commit package-lock.json 的 repo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="tldr"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;事實上這樣在 library 上沒有什麼問題。Lockfile 的追蹤有個小原則：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;應用程式建議追蹤 lockfile：不追蹤，下次 npm install 就無法確定具體的依賴版本是什麼。&lt;/li&gt;
&lt;li&gt;函式庫可以不用追蹤，因為使用者安裝套件時，套件管理器會根據依賴自動選取最適合的版本，而你自己的 lockfile &lt;strong&gt;會被忽略&lt;/strong&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;。不過建議追蹤，見下文。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="函式庫該不該追蹤-lockfile"&gt;函式庫「該不該」追蹤 lockfile？&lt;/h2&gt;
&lt;p&gt;假如函式庫有用到諸如 ESLint 的工具，追蹤一下可以避免之後設定開發依賴的麻煩，
所以像 NPM 官方就是推薦 &lt;strong&gt;無論如何都追蹤 lockfile&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;不過也有預設不推薦在 library 情境下追蹤 lock 的例子，比如 Rust 的 Cargo 套件管理器&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;。不過 Cargo 的開發工具主要都是作為 submodule 安裝在系統中，通常不會跟著 repo 一起追蹤，所以不太適合放在一起比較。&lt;/p&gt;
&lt;p&gt;不過要注意：&lt;strong&gt;這時候的 lockfile 就不是追給下游應用程式看的&lt;/strong&gt;，主要是為了自己開發方便。&lt;/p&gt;
&lt;h2 id="為什麼應用程式就該追蹤-lockfile"&gt;為什麼「應用程式」就該追蹤 lockfile？&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 通常不是描述固定的版本，而是一個版本區間：舉個例子：你可能在 &lt;code&gt;package.json&lt;/code&gt; 裡面描述 &lt;code&gt;vue: &amp;quot;^2.4.0&amp;quot;&lt;/code&gt;，但實際上 NPM 幫你選了 &lt;code&gt;2.7.13&lt;/code&gt;。這個行為是可以預測的，可以參考 NPM 官方的 Semver 計算機：&lt;a href="https://semver.npmjs.com"&gt;https://semver.npmjs.com&lt;/a&gt;。&lt;/p&gt;</description></item><item><title>#Mac #軟體 威注音 v2.8.5：比原廠注音更智慧、比競品更好看的注音輸入法</title><link>https://blog.pan93.com/posts/vchewing-introduction/</link><pubDate>Sun, 02 Oct 2022 14:58:00 +0000</pubDate><guid>https://blog.pan93.com/posts/vchewing-introduction/</guid><description>&lt;blockquote&gt;
&lt;p&gt;原文撰於 &lt;a href="https://www.dcard.tw/f/apple/p/240146476"&gt;Dcard&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="abstract"&gt;Abstract&lt;/h2&gt;
&lt;p&gt;「威注音」是一款主打智慧 🤖、美觀 🎨、高自訂性 🔧，且追求完美  的注音輸入法。正在為錯字百出的選字、操作習慣、簡體輸入，甚至是原廠注音遲遲不修正的 bug 所苦嗎？威注音或許可以讓你對注音打字改觀 🥳。&lt;/p&gt;
&lt;p&gt;威注音自己覺得有八個值得你使用的理由（建議搭配宣傳海報閱讀）：&lt;/p&gt;
&lt;p&gt;&lt;img alt="威注音的宣傳海報" loading="lazy" src="https://assets.blog.pan93.com/vchewing-introduction/8%20Reason%20to%20Use%20vChewing.png"&gt;&lt;/p&gt;
&lt;h2 id="最佳化打字體驗-"&gt;最佳化打字體驗 ⌨️&lt;/h2&gt;
&lt;p&gt;還再打出錯字擺出的句子，成為朋友對話之間的笑柄、公司討論之間的尷尬嗎 😅？或許你可以試試看威注音輸入法。&lt;/p&gt;
&lt;p&gt;威注音輸入法的詞庫收錄了三千餘條（且持續增加中的）常用詞組，因此輸入常用詞彙時可以減少選字的次數 🙌🏼。&lt;/p&gt;
&lt;p&gt;目前威注音的詞庫仍在持續更新、拓展，因此威注音將會變得愈來愈智慧。如果你有任何想納入官方詞庫的常用字彙，也可以在這篇底下（或到 GitHub 下）留言提議加入～&lt;/p&gt;
&lt;p&gt;&lt;img alt="打字幾乎不用選字！" loading="lazy" src="https://assets.blog.pan93.com/vchewing-introduction/p1.webp"&gt;&lt;/p&gt;
&lt;h2 id="可以自訂語彙-"&gt;可以自訂語彙 💬&lt;/h2&gt;
&lt;p&gt;覺得每次輸入自己或朋友的名字，或者是科系名稱都得選字很煩嗎？威注音提供了一套非常簡單的自訂詞彙新增方式，簡而言之就是「選字後選取，按 Return 存檔。」可以看看下方影片的操作：&lt;/p&gt;
&lt;p&gt;&lt;img alt="使用鍵盤即可輕鬆增刪詞彙" loading="lazy" src="https://assets.blog.pan93.com/vchewing-introduction/p2.mp4"&gt;&lt;/p&gt;
&lt;h2 id="原生繁簡輸入零轉換"&gt;原生繁簡輸入、零轉換&lt;/h2&gt;
&lt;p&gt;蘋果內建的輸入法「不支援簡體輸出模式」——想像每次跟對岸朋友洽談事情，每一句都得使用特色選單裡面的簡體轉換功能轉換 😱……就算你不煩，觸控板應該也挺累的 😰。&lt;/p&gt;
&lt;p&gt;威注音除了提供原生的簡體輸出模式，輸出簡體只要切換輸入法即可一步到位之外，威注音的詞庫也是簡體中文與繁體中文各自一份的：方便簡體中文母語者「在不熟悉台澎金馬的審音與習慣讀音的前提下」也可以順利使用威注音敲字。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;P.S.: 威注音的繁體中文輸入模式可以使用「轉換至康熙繁體」「轉換至 JIS 繁體」功能。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="輸入法選單中有「繁」「簡」兩種模式可以切換" loading="lazy" src="https://assets.blog.pan93.com/vchewing-introduction/p3.webp"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="繁體中文和簡體中文的輸入畫面" loading="lazy" src="https://assets.blog.pan93.com/vchewing-introduction/p3-1.webp"&gt;&lt;/p&gt;
&lt;h2 id="精緻選字窗框"&gt;精緻選字窗（框）&lt;/h2&gt;
&lt;p&gt;我覺得這是威注音和其他競品比較下的最直觀第一感受了。當其他競品的選字窗還停留在 Windows XP 的土裡土氣、甚至沒有暗色模式的時候；威注音的選字窗已經和蘋果原生相當相似，甚至可以啟用「IMK 選字窗」來得到和原生注音同款的介面效果 🧑‍🎨。&lt;/p&gt;
&lt;p&gt;&lt;img alt="vChewing 的「田所」選字窗，和可選啟用的原廠 IMK 選字窗" loading="lazy" src="https://assets.blog.pan93.com/vchewing-introduction/p4.webp"&gt;&lt;/p&gt;
&lt;p&gt;IMK 選字窗可以在威注音的「開發道場」開啟。不過威注音自己做的田所矩陣選字框，我覺得已經很接近蘋果的 UI 效果了。&lt;/p&gt;
&lt;h2 id="極高自訂可玩性"&gt;極高自訂、可玩性&lt;/h2&gt;
&lt;p&gt;你想用 Shift 切換中英文嗎？預設開啟 🎉；你只想用「中/英」切換鍵切換語言嗎？也可以關閉 Shift 的切換。&lt;/p&gt;
&lt;p&gt;你想要輸入罕見到原廠注音打不出來的字嗎？威注音可以讓你啟用「CNS11643 全字庫」，在安裝正確字體後即可直接打出符合政府編碼標準的罕見字 🎉。&lt;/p&gt;
&lt;p&gt;覺得選字窗的候選字太小，看不清楚嗎？威注音支援把選字窗文字放到原先 6 倍大 (96px) 的大小 🎉。&lt;/p&gt;</description></item><item><title>The comparison of Linter and Formatter</title><link>https://blog.pan93.com/posts/linter-vs-formatter/</link><pubDate>Fri, 09 Sep 2022 20:39:00 +0800</pubDate><guid>https://blog.pan93.com/posts/linter-vs-formatter/</guid><description>&lt;p&gt;&lt;img alt="Main image: Linter vs Formatter" loading="lazy" src="https://assets.blog.pan93.com/linter-vs-formatter/linter-v-formatter.png"&gt;&lt;/p&gt;
&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;src: &lt;a href="https://twitter.com/bystartw/status/1568217424658583553?s=61&amp;amp;t=UVzhpuQaGcgZkGpySz_yVQ"&gt;https://twitter.com/bystartw/status/1568217424658583553?s=61&amp;amp;t=UVzhpuQaGcgZkGpySz_yVQ&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Noticing some developers are confused with “Linter” and “Formatter,” I made a simple figure showing what messages Linter and Formatter prefer to print.&lt;/p&gt;
&lt;p&gt;Thanks to @nonoesp for &lt;a href="https://nono.ma/linter-vs-formatter"&gt;his incisive introduction to Linter and Formatter&lt;/a&gt;!&lt;/p&gt;</description></item><item><title>vChewing 出了 2.4.0 SP2 了！</title><link>https://blog.pan93.com/posts/vchewing-2-4-0/</link><pubDate>Wed, 07 Sep 2022 01:17:00 +0000</pubDate><guid>https://blog.pan93.com/posts/vchewing-2-4-0/</guid><description>&lt;blockquote&gt;
&lt;p&gt;src: &lt;a href="https://twitter.com/bystartw/status/1567200310430662656?s=61&amp;amp;t=UVzhpuQaGcgZkGpySz_yVQ"&gt;https://twitter.com/bystartw/status/1567200310430662656?s=61&amp;amp;t=UVzhpuQaGcgZkGpySz_yVQ&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;vChewing 出了 2.4.0 SP2 了！🎉&lt;/p&gt;
&lt;p&gt;自己覺得 vChewing 是除了官方注音之外，長得最好看（因為有 IMK 加持），而且功能跟選字功能也很棒的輸入法，真的很推薦想嘗試第三方輸入法的朋友試試看！（連結見引文）&lt;/p&gt;
&lt;p&gt;&lt;img alt="vChewing 在 2.4.0 SP2 推出時的 commit 提交情況" loading="lazy" src="https://assets.blog.pan93.com/vchewing-2-4-0/activity.png"&gt;
Figure 1: vChewing 提交很活躍！&lt;/p&gt;
&lt;h2 id="資安疑慮"&gt;資安疑慮&lt;/h2&gt;
&lt;p&gt;另外關於 vChewing (下稱 vC) 的資安或隱私疑慮：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vC 在 2.3.0 之後引入 Apple 官方的沙盒機制，因此只要你沒有授予授權，輸入法本身是看不到你的資料夾的。&lt;/li&gt;
&lt;li&gt;雖然我沒有完全閱讀 vC 的 src，但至少它的 Shift 是靠旁敲的方式偵測的，不是記錄所有鍵盤輸入&lt;/li&gt;
&lt;li&gt;vC 只有更新和網站相關有連到個人站台&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="稽核方式"&gt;稽核方式&lt;/h2&gt;
&lt;p&gt;如果有興趣稽核 vChewing 外連的情況，可以自己 clone 回來用 regex 查，或者是直接用這個第三方網站看：
&lt;a href="https://sourcegraph.com/search?q=context:global+repo:vChewing/%28vChewing-macOS%7CTekkon%7CMegrez%7CHotenka%7Clibvchewing-data%29+%28http%7Chttps%7Cftp%7Cws%7Cwss%29%5C:&amp;amp;patternType=regexp"&gt;sourcegraph&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;** 如果還是信不過（認為作者會混淆連結）的話，也可以自己掛一個抓包軟體偵測 vC 的所有請求啦…… 雖然我是找不到除了更新以外的請求 code：&lt;a href="https://sourcegraph.com/search?q=context:global+repo:vChewing/%28vChewing-macOS%7CTekkon%7CMegrez%7CHotenka%7Clibvchewing-data%29+%28%28en%7Cde%29%28crypt%7Ccode%29%28ion%7C%29%29%7C%28base64%7Cbase32%7Caes%7Copenssl%7CSymmetricKey%7Cstream%7CSession%7CConnection%7CRequest%7CResponse%29+count:%22all%22&amp;amp;patternType=regexp"&gt;sourcegraph&lt;/a&gt;&lt;/p&gt;</description></item><item><title>善用 Swift 的字串擷取功能簡化 I18n 流程</title><link>https://blog.pan93.com/posts/swift-use-compiler-to-extract-strings/</link><pubDate>Sat, 27 Aug 2022 19:47:25 +0800</pubDate><guid>https://blog.pan93.com/posts/swift-use-compiler-to-extract-strings/</guid><description>&lt;h2 id="撰文原由"&gt;撰文原由&lt;/h2&gt;
&lt;p&gt;在 &lt;a href="https://github.com/vChewing/vChewing-macOS/releases/tag/2.1.0"&gt;威注音 2.1.0 SP1 的更新日誌&lt;/a&gt; 下看到了這麼一段話：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interface Builder 給每個標籤的命名都是隨機的，手動改起來又低效又容易改壞，對多語言本地化而言簡直是天災。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://www.wwdcnotes.com/notes/wwdc21/10220/"&gt;事實上在 WWDC21 中&lt;/a&gt;，Apple 就為 Xcode 推出了一項可以解決這項痛點的新功能：「使用編譯器擷取字串」。與早期透過從 Swift 原始碼拉取可翻譯字串，經常漏掉一些字串的方式不同——這個功能會先編譯原始碼，然後從編譯出的檔案中判斷可以翻譯的字串。&lt;/p&gt;
&lt;h2 id="啟用功能"&gt;啟用功能&lt;/h2&gt;
&lt;p&gt;這項功能在新專案是預設啟用的，但舊專案可以 opt-in：點開專案設定，進入 “Build Settings”，展開所有功能（All），尋找 “Swift” 然後找到 Localization 的 “Use Compiler to Extract Swift Strings”，將其設定為 Yes 即可。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Opt in “Use Compiler to Extract Swift Strings”" loading="lazy" src="https://assets.blog.pan93.com/swift-use-compiler-to-extract-strings/opt-in-function.webp"&gt;&lt;/p&gt;
&lt;h2 id="匯出字串"&gt;匯出字串&lt;/h2&gt;
&lt;p&gt;開啟之後的國際化與本地化方式，跟先前會不太一樣。以往需要手動改 strings 檔案，現在可以先到 “Product” &amp;gt; “Export Localization…” 匯出編譯器擷取出的所有字串：&lt;/p&gt;
&lt;p&gt;&lt;img alt="“Product” &amp;gt; “Export Localization…”" loading="lazy" src="https://assets.blog.pan93.com/swift-use-compiler-to-extract-strings/menu-export-localization.webp"&gt;&lt;/p&gt;
&lt;p&gt;接下來指定匯出的路徑，然後等待 Swift 完成編譯並擷取字串。若是從舊專案 opt-in，擷取字串的過程中可能會拋出一些錯誤，這個時候就得修正（如果只是警告的話也可以忽略）：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Exporting…" loading="lazy" src="https://assets.blog.pan93.com/swift-use-compiler-to-extract-strings/exporting.webp"&gt;&lt;/p&gt;
&lt;h2 id="翻譯流程"&gt;翻譯流程&lt;/h2&gt;
&lt;p&gt;接下來就可以進入選擇好的資料夾，使用 Xcode 點開對應語系的 xcloc 檔案：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Open the xcloc file with Xcode" loading="lazy" src="https://assets.blog.pan93.com/swift-use-compiler-to-extract-strings/open-xcloc.webp"&gt;&lt;/p&gt;</description></item><item><title>Rust for everything</title><link>https://blog.pan93.com/posts/rust-for-everything/</link><pubDate>Fri, 12 Aug 2022 01:44:41 +0800</pubDate><guid>https://blog.pan93.com/posts/rust-for-everything/</guid><description>&lt;p&gt;src: &lt;a href="https://redd.it/wllpa4"&gt;https://redd.it/wllpa4&lt;/a&gt; (&lt;a href="https://t.me/programmer_humor/38462"&gt;https://t.me/programmer_humor/38462&lt;/a&gt;)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;low-level programming&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://os.phil-opp.com/"&gt;Rust&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;game development&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://arewegameyet.rs/"&gt;Rust&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;high performance code&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/box-plot-summary-charts.html"&gt;Rust&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;scientific programming&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="http://pola.rs"&gt;Rust&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;machine learning&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/vaaaaanquish/Awesome-Rust-MachineLearning"&gt;Rust&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;front-end web development&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://yew.rs/"&gt;Rust&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;back-end web development&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/tokio-rs/axum"&gt;Rust&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You only need &lt;strong&gt;ONE&lt;/strong&gt; languages for everything: &lt;strong&gt;&lt;em&gt;Rust&lt;/em&gt;&lt;/strong&gt;. You don&amp;rsquo;t need more.&lt;/p&gt;
&lt;p&gt;&lt;img alt="You only need three languages for everything: C++, Python\nand Javascript. You don&amp;rsquo;t need more." loading="lazy" src="https://assets.blog.pan93.com/rust-for-everything/uwu.webp"&gt;&lt;/p&gt;</description></item><item><title>文科生也能懂的 Rust async 机制</title><link>https://blog.pan93.com/posts/what-is-rust-async-cn/</link><pubDate>Sun, 07 Aug 2022 21:12:45 +0000</pubDate><guid>https://blog.pan93.com/posts/what-is-rust-async-cn/</guid><description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://twitter.com/repsiace/status/1554103778994900992/"&gt;https://twitter.com/repsiace/status/1554103778994900992/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Rust async 到底是什么啊啊啊啊" loading="lazy" src="https://assets.blog.pan93.com/what-is-rust-async/20220807130414.webp"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;修改一下：work stealing, thread-per-core, waker, mpsc, task queue 只有他们懂&amp;hellip; 正常人不可能看懂 – &lt;a href="https://twitter.com/twicemoemoe/status/1554305217134735362"&gt;@twicemoemoe, 22-08-02&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;作为一个文科生，其实觉得 async 真的没有想象中的这么困难 ⋯⋯ 😂 或许搭配一些图片会好懂很多吧。&lt;/p&gt;
&lt;h2 id="tldr-不废话版本"&gt;TL;DR 不废话版本&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sync&lt;/strong&gt;（同步）：一件事情做完之后，再做下一件事情。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;blocking&lt;/strong&gt;（堵塞）：指“等一件事情”的行为。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Async&lt;/strong&gt;（异步）：一件事情还没完成，可以做其他不冲突的事情。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;concurrency&lt;/strong&gt;（并发）：程序 &lt;strong&gt;架构&lt;/strong&gt; 中，各个任务可以 &lt;strong&gt;独立运行&lt;/strong&gt; 的特性。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;future&lt;/strong&gt;：Rust 中的一个异步任务的表示。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;polling&lt;/strong&gt;：不停地询问任务，确认事情是否已经完成。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;event-driven&lt;/strong&gt;：事情完成后，任务自己发通知表明完成。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;parallelism&lt;/strong&gt;（并行）：同时 &lt;strong&gt;运行&lt;/strong&gt; 数个程序的行为。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;thread&lt;/strong&gt;（线程、线程）：系统进程（任务集）的基本单元。thread 通常是交由 CPU 内核运行。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;spawn&lt;/strong&gt;（生成）：指产生 thread 的行为。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;thread pool&lt;/strong&gt;（线程池）：将 thread 高效分配给每个任务的地方。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Async runtime&lt;/strong&gt;: 以 tokio 为例
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;join&lt;/strong&gt; (macro)：并发运行 async 函数，并在全部完成后回传。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;select&lt;/strong&gt; (macro)：哪个 async 函数快，回传那个 async 函数的结果。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;main&lt;/strong&gt; (attribute macro)：在 main() 初始化 runtime。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;block_on&lt;/strong&gt;：在 sync 上运行 async 函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;spawn&lt;/strong&gt;：并行运行 async 函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;spawn_blocking&lt;/strong&gt;：在异步函数里面，为一个高耗时且同步 (blocking) 的函数另辟新线程 (thread)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="同步-synchronous-跟异步-asynchoronous"&gt;同步 (Synchronous) 跟异步 (Asynchoronous)&lt;/h2&gt;
&lt;p&gt;“&lt;strong&gt;同步&lt;/strong&gt;”就是整个程序等一件事情完成（&lt;strong&gt;blocking&lt;/strong&gt;，堵塞）。“&lt;strong&gt;异步&lt;/strong&gt;”则是一件事情还没完成，可以做其他不冲突的事情。&lt;/p&gt;</description></item><item><title>文組也能懂的 Rust async 機制</title><link>https://blog.pan93.com/posts/what-is-rust-async/</link><pubDate>Sun, 07 Aug 2022 13:03:20 +0000</pubDate><guid>https://blog.pan93.com/posts/what-is-rust-async/</guid><description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://twitter.com/repsiace/status/1554103778994900992/"&gt;https://twitter.com/repsiace/status/1554103778994900992/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Rust async 到底是什麼啊啊啊啊" loading="lazy" src="https://assets.blog.pan93.com/what-is-rust-async/20220807130414.webp"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;修改一下：work stealing, thread-per-core, waker, mpsc, task queue 只有他们懂&amp;hellip; 正常人不可能看懂 – &lt;a href="https://twitter.com/twicemoemoe/status/1554305217134735362"&gt;@twicemoemoe, 22-08-02&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;作為一個文組，其實覺得 async 真的沒有想像中的這麼困難 ⋯⋯ 😂 或許搭配一些圖片會好懂很多吧。&lt;/p&gt;
&lt;h2 id="tldr-不廢話版本"&gt;TL;DR 不廢話版本&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sync&lt;/strong&gt;（同步）：一件事情做完之後，再做下一件事情。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;blocking&lt;/strong&gt;（堵塞）：指「等一件事情」的行為。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Async&lt;/strong&gt;（非同步）：一件事情還沒完成，可以做其他不衝突的事情。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;concurrency&lt;/strong&gt;（並行、併發）：程式 &lt;strong&gt;架構&lt;/strong&gt; 中，各個任務可以 &lt;strong&gt;獨立執行&lt;/strong&gt; 的特性。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;future&lt;/strong&gt;：Rust 中的一個非同步任務的表示。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;polling&lt;/strong&gt;：不停地詢問任務，確認事情是否已經完成。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;event-driven&lt;/strong&gt;：事情完成後，任務自己發通知表明完成。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;parallelism&lt;/strong&gt;（平行）：同時 &lt;strong&gt;執行&lt;/strong&gt; 數個程式的行為。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;thread&lt;/strong&gt;（執行緒、線程）：系統處理程式（任務集）的基本單元。thread 通常是交由 CPU 核心執行。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;spawn&lt;/strong&gt;（生成）：指產生 thread 的行為。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;thread pool&lt;/strong&gt;（執行緒池）：將 thread 高效分配給每個任務的地方。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Async runtime&lt;/strong&gt;: 以 tokio 為例
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;join&lt;/strong&gt; (macro)：並行執行 async 函數，並在全部完成後回傳。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;select&lt;/strong&gt; (macro)：哪個 async 函數快，回傳那個 async 函數的結果。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;main&lt;/strong&gt; (attribute macro)：在 main() 初始化 runtime。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;block_on&lt;/strong&gt;：在 sync 上執行 async 函數。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;spawn&lt;/strong&gt;：平行執行 async 函數。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;spawn_blocking&lt;/strong&gt;：在非同步函數裡面，為一個高耗時且同步 (blocking) 的函數另闢新執行緒 (thread)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="同步-synchronous-跟非同步-asynchoronous"&gt;同步 (Synchronous) 跟非同步 (Asynchoronous)&lt;/h2&gt;
&lt;p&gt;「&lt;strong&gt;同步&lt;/strong&gt;」就是整個程式等一件事情完成（&lt;strong&gt;blocking&lt;/strong&gt;，堵塞）。「&lt;strong&gt;非同步&lt;/strong&gt;」則是一件事情還沒完成，可以做其他不衝突的事情。&lt;/p&gt;</description></item><item><title>該選名聲未知的科大，還是私立普大？</title><link>https://blog.pan93.com/posts/college-or-university/</link><pubDate>Mon, 07 Mar 2022 09:01:42 +0000</pubDate><guid>https://blog.pan93.com/posts/college-or-university/</guid><description>&lt;blockquote&gt;
&lt;p&gt;原問題在「學霸模擬機」。放在 Blog 純粹是為了更大的排版自由。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="tldr---核心問題"&gt;TL;DR - 核心問題&lt;/h2&gt;
&lt;p&gt;究竟該走「已經特選上」，但「未知名聲」的科技大學；還是放棄科大，繼續準備個申，走傳統的私立大學？&lt;/p&gt;
&lt;h2 id="簡短自我介紹"&gt;簡短自我介紹&lt;/h2&gt;
&lt;p&gt;我目前是普高生，但對程式實作有濃厚的興趣。目前是幾個知名專案的維護者或共同創辦者，能力也在幾間大學的特殊選才中獲得肯定（書審都過）。&lt;/p&gt;
&lt;p&gt;因為數學和自然上的短板，我是社會組，且想要走偏資的資管（數學不重，同時可以滿足我程式探索的興趣）。經過深入比較，我投了包含 &lt;strong&gt;高雄科技大學&lt;/strong&gt; 的 &lt;strong&gt;智慧商務系&lt;/strong&gt; 的幾間科技大學，而我最終也上了。&lt;/p&gt;
&lt;h2 id="這間科大的優點在哪裡"&gt;這間科大的優點在哪裡？&lt;/h2&gt;
&lt;p&gt;根據某學校資料，智慧商務系的前身，是偏資訊的 &lt;strong&gt;資訊管理系&lt;/strong&gt;。我有看過課程大綱，且我對這個科系的課程 &lt;strong&gt;頗有興趣&lt;/strong&gt;。而且身為高雄人，若我讀這間科技大學的話，不僅 &lt;strong&gt;學費比較省&lt;/strong&gt;（國立學費通常比私立來得便宜），而且也可以 &lt;strong&gt;省下通勤和在外租房的費用&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="那這間科大的缺點是什麼"&gt;那這間科大的缺點是什麼？&lt;/h2&gt;
&lt;p&gt;其實不全然是這間科大的問題。&lt;/p&gt;
&lt;p&gt;這間科大特選通過第二階段的有 8 個人（3 正取 / 5 備取）。然而這 8 個人當中，&lt;strong&gt;有 5 個人（62.5%）選擇放棄資格&lt;/strong&gt;。最近我聽聞剩下 3 個人當中，有一個也想要放棄。雖然我確定最後一個人是因為人生規劃而萌生放棄想法，但這也難免會讓我懷疑：&lt;strong&gt;「這科系是不是沒人想讀？」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;其實我有認識一位就讀本科系的學長，據他所說：&lt;strong&gt;「這個科系比較自由，更適合你發展自己想要的東西」。&lt;/strong&gt; 我不擅長讀言下之意，雖然我喜歡給予比較多自學空間的學校，但他的意思也有沒有可能是 &lt;strong&gt;科系課程有點水？&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="學測成績及落點可上的大學"&gt;學測成績，及落點可上的大學&lt;/h2&gt;
&lt;p&gt;我的學測成績如下：（# = 未考）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;國&lt;/th&gt;
&lt;th&gt;英&lt;/th&gt;
&lt;th&gt;數 A&lt;/th&gt;
&lt;th&gt;數 B&lt;/th&gt;
&lt;th&gt;社&lt;/th&gt;
&lt;th&gt;自&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;#&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;此外英聽 B、APCS 觀念 3 實作 2。使用 &lt;a href="https://www.com.tw"&gt;https://www.com.tw&lt;/a&gt; 進行落點分析，國立和私立的大致情況如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;國立：[地名大學] 如屏東大學、臺北大學&lt;/li&gt;
&lt;li&gt;私立：[傳統大學] 如淡江大學、輔仁大學&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="別人的想法是什麼"&gt;別人的想法是什麼？&lt;/h2&gt;
&lt;p&gt;我問過其中一位老師，他覺得出外縣市（即便是私立大學）比較能拓展視野，也能獲得比較多的資源。&lt;/p&gt;
&lt;p&gt;其他大部分朋友的想法都是「你喜歡哪一個，就去哪一間學校。」我挺滿意現況，但畢竟 &lt;strong&gt;我只看過課程大綱，以及聽學長姐的說法&lt;/strong&gt;。我擔心我會不會因為不夠瞭解這間科系（&lt;strong&gt;資訊不對等&lt;/strong&gt;），而給自己留下遺憾。&lt;/p&gt;</description></item><item><title>Rust 的 crate/super/self 關係</title><link>https://blog.pan93.com/posts/rust-mod-relation/</link><pubDate>Wed, 13 Oct 2021 11:06:21 +0800</pubDate><guid>https://blog.pan93.com/posts/rust-mod-relation/</guid><description>&lt;p&gt;&lt;img alt="&lt;code&gt;rust_hello_world&lt;/code&gt; 的架構圖，與 crate、super、self 的關係" loading="lazy" src="https://assets.blog.pan93.com/rust-mod-relation/crate-relation.png"&gt;&lt;/p&gt;
&lt;p&gt;假設 &lt;code&gt;rust_hello_world&lt;/code&gt; 的目錄架構長這樣：（範例源自於我手邊的某個 production 專案）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;rust_hello_world
&lt;ul&gt;
&lt;li&gt;Cargo.toml&lt;/li&gt;
&lt;li&gt;src
&lt;ul&gt;
&lt;li&gt;cli
&lt;ul&gt;
&lt;li&gt;opt.rs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;lib.rs&lt;/li&gt;
&lt;li&gt;cli.rs&lt;/li&gt;
&lt;li&gt;logger.rs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「crate」概括來說就是一個專案，用 &lt;code&gt;Cargo.toml&lt;/code&gt; 區分。每個 &lt;code&gt;mod&lt;/code&gt; 都是一個層級，&lt;code&gt;super&lt;/code&gt; 就是上個層級，&lt;code&gt;self&lt;/code&gt; 就是本層級。上面的圖已經把 crate / super / self 的對應關係寫得很清楚了，以下寫範例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cli::opt::Opt&lt;/code&gt; 想要讀取 &lt;code&gt;LoggingLevel&lt;/code&gt;，路徑可以這樣走：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;super::super::logger::LoggerLevel&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crate::logger::LoggerLevel&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cli::opt::Opt&lt;/code&gt; 想要讀取 &lt;code&gt;PROG_NAME&lt;/code&gt;，路徑可以這樣走：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;super::PROG_NAME&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crate::cli::PROG_NAME&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;事實上 Rust 的模組關係也沒這麼複雜。把上面的例子變成目錄：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[crate] /
&lt;ul&gt;
&lt;li&gt;[mod] cli/
&lt;ul&gt;
&lt;li&gt;[mod] opt/
&lt;ul&gt;
&lt;li&gt;[struct] Opt.txt&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;[const &amp;amp;str] PROG_NAME.txt&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;[[pub] mod] logger/
&lt;ul&gt;
&lt;li&gt;[[pub] enum] LoggingLevel.txt&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;super&lt;/code&gt; 等價於 &lt;code&gt;..&lt;/code&gt;，self 等價於 &lt;code&gt;.&lt;/code&gt;，而 crate 則類似 &lt;code&gt;/&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>為何退租 VPS。</title><link>https://blog.pan93.com/posts/cancel-my-vps/</link><pubDate>Tue, 04 May 2021 12:28:52 +0000</pubDate><guid>https://blog.pan93.com/posts/cancel-my-vps/</guid><description>&lt;p&gt;是時候退租自己的 Contabo VPS 了。這台主機陪了我 7 個月，陪伴我寫了非常多篇文章，要跟它分離，著實不捨。&lt;/p&gt;
&lt;p&gt;然而，我思考了很久，除了放部落格和一些很沒必要的服務之外，我真想不到這筆支出的必要性。很多東西都可以用免費的服務代替，真的沒有必要為了這幾個服務和所謂的情感，每三個月花 500 多續租下去。&lt;/p&gt;
&lt;p&gt;這讓我想到「極簡主義」——斷捨離造就乾淨，依依不捨只會佔據空間。我的主機不也一樣——為什麼要因為情感而租沒必要的東西呢？依依不捨也難道不是徒增支出的浪費和主機商資源的浪費？&lt;/p&gt;
&lt;p&gt;支出這種東西，理性大於感性會比較好。退租吧。&lt;/p&gt;</description></item><item><title>未成年在 Upwork 活該拿不回錢。</title><link>https://blog.pan93.com/posts/upwork-withdraw/</link><pubDate>Mon, 11 Jan 2021 00:05:13 +0000</pubDate><guid>https://blog.pan93.com/posts/upwork-withdraw/</guid><description>&lt;h2 id="tldr"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;我因未通過身份驗證，無法領回自己已經在 Upwork 賺到的錢。理由是未滿 18 歲，但是 Upwork 事先沒有任何明確的標示，即使我沒看 ToS 錯在先，但這樣使我覺得很不公平。&lt;/p&gt;
&lt;h2 id="正文"&gt;正文&lt;/h2&gt;
&lt;p&gt;我大概 11 月左右想為自己購置一台效能更高的電腦，而看中了 MacBook。想著「自己的東西自己賺」，我決定找一個 Freelancer 平台賺取筆電的錢。&lt;/p&gt;
&lt;p&gt;在看過國內和國外的 Freelancer 自由業平台之後，我決定了聲稱 Microsoft 都在使用的 &lt;strong&gt;Upwork&lt;/strong&gt;。註冊時 Upwork 完全沒有年齡確認，我當初以為 Upwork 開放給未成年使用。註冊要求確認 ToS 的部分，&lt;strong&gt;我沒認真看卻勾選「我同意」了&lt;/strong&gt;，我沒想過幾天後我會因此想痛罵我自己。&lt;/p&gt;
&lt;p&gt;自介填完後，我便開始尋找工作。其中一個 $50 USD 的案子我接成了，並且對方也著實把案子交給我了。案子做了一陣子後，跳出了一個小提示：&lt;/p&gt;
&lt;p&gt;&lt;img alt="在 2020 年 12 月 9 日前驗證您的身份後，就能繼續在 Upwork 找尋工作機會。(2021-1-10)" loading="lazy" src="https://assets.blog.pan93.com/upwork-withdraw/%E6%88%AA%E5%9C%96-2021-01-10-%E4%B8%8B%E5%8D%8810.41.51.webp"&gt;&lt;/p&gt;
&lt;p&gt;在 2020 年 12 月 9 日前驗證您的身份後，就能繼續在 Upwork 找尋工作機會。(2021-1-10)&lt;/p&gt;
&lt;p&gt;按下「Get started」認證後，我卻卡關了。&lt;/p&gt;
&lt;p&gt;&lt;img alt="生日 &amp;gt; 您必須至少有 18 歲。 (2021-1-10)" loading="lazy" src="https://assets.blog.pan93.com/upwork-withdraw/%E6%88%AA%E5%9C%96-2021-01-10-%E4%B8%8B%E5%8D%8810.42.30.webp"&gt;&lt;/p&gt;
&lt;p&gt;生日 &amp;gt; 您必須至少有 18 歲。 (2021-1-10)&lt;/p&gt;
&lt;p&gt;如字面意思所示，我不滿 18 歲所以過不了。但是案子都接了，甚至都快寫完了，我便沒在 Upwork 按下「Withdraw」收回案子了。做完之後，對方也撥給我錢了，&lt;strong&gt;我才發現到沒驗證會拿不回錢&lt;/strong&gt;（withdraw earning）。但是我當時認為是自己是看錯了，就等 Upwork 在我設定的 12 月底把錢匯進去我的 PayPal 帳戶。&lt;/p&gt;</description></item><item><title>評論：「行」，還是「列」？</title><link>https://blog.pan93.com/posts/row-column-in-taiwanese/</link><pubDate>Sat, 09 Jan 2021 13:48:50 +0800</pubDate><guid>https://blog.pan93.com/posts/row-column-in-taiwanese/</guid><description>&lt;p&gt;很高興我的數學老師有正確解釋「列」跟「行」。他的解釋很好——大部分人常會說「一列車廂」，而列車在你眼前都是橫向移動，所以「列」是橫的。畢竟你不會站在行駛中的列車前，看到的必定都是左到右移動，而且台灣沒人講「一行火車」。&lt;/p&gt;
&lt;p&gt;不過在觀察同儕間的回答時，我發現到一堆人認為「列」是行，「行」是列。不知道是搞不清楚，還是受到中國那邊的用詞影響呢？&lt;/p&gt;
&lt;p&gt;所以，我在翻某些針對普羅大眾的軟體時，我都會盡可能不要提到這兩個字，保持「row」和「column」，因為對於某些不清楚這些用詞的人，硬塞正確的用法會造成誤解。&lt;/p&gt;
&lt;p&gt;當然，如果是針對部分現代被中國文化影響較重的青少年，就例如我段落開頭說到的那個案例。那些人可能也不知道 row 和 column 是什麼，所以我就比較傾向保持中國那邊的講法，也就是 row = 行，column = 列。因為這要解釋起來太麻煩了，且多數人相信中國的用法才是正確的。&lt;/p&gt;
&lt;p&gt;但是這樣治標不治本，台灣應該加強台灣本土用詞的教育與指正。而非放行中國用詞，讓新一代年輕人帶著從抖音、微信學來的用詞到處跑。&lt;/p&gt;</description></item><item><title>109 高中資訊學科能力複賽的參加心得</title><link>https://blog.pan93.com/posts/toi-competition-opinions/</link><pubDate>Mon, 02 Nov 2020 08:57:07 +0000</pubDate><guid>https://blog.pan93.com/posts/toi-competition-opinions/</guid><description>&lt;p&gt;&lt;strong&gt;又墊底了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但不一樣的是，&lt;strong&gt;這次比賽我收穫上不少&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="早先的選手培訓課程"&gt;早先的選手培訓課程&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;這是我覺得整個競賽當中，我最印象深刻而且最有收穫的課程。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在參加這門課程之前，其實我很厭惡演算法。因為數學跟邏輯不太好，所以我當時會覺得演算法很恐怖，看不懂，不敢學。但在上完這幾門課程後，我才發現到，&lt;strong&gt;很多演算法其實自己平時就在用，只是從未察覺。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;就例如動態規劃 (Dynamic Programming) 好了，概念這就像是我平常在搞得快取 (Cache)，把先前擷取 / 計算過的結果儲存下來，下次要用的時候，就直接把之前擷取 / 計算的結果拿出來。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hashmap cache_db;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;get_something(id) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; declare data;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if id not in cache_db
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data = _get_something(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cache_db[id] = data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; else
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data = cache_db[id]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;還有圖論，這對我來講是個很新穎的演算法概念，而在聽邏輯之後覺得比想像中還要好懂。「廣度優先」和「深入優先」這兩個抽象的名詞，用老師的 GIF 講解之後就變得很好懂。但就是還不知道能用在哪些地方 😅&lt;/p&gt;
&lt;p&gt;最後是逐個擊破（分而治，D&amp;amp;C）和貪婪演算法 (Greedy Method)，雖然一開始沒聽懂，但後來自己回家找了 Wikipedia 之後就看懂了。也是個自己寫程式偶爾會想到，但沒發覺是個演算法的東西。&lt;/p&gt;
&lt;p&gt;&lt;img alt="培訓課程的筆記 - 1" loading="lazy" src="https://assets.blog.pan93.com/toi-competition-opitions/20201115_115415.webp"&gt;
&lt;img alt="培訓課程的筆記 - 2" loading="lazy" src="https://assets.blog.pan93.com/toi-competition-opitions/20201115_115345.webp"&gt;
&lt;img alt="培訓課程的筆記 - 3" loading="lazy" src="https://assets.blog.pan93.com/toi-competition-opitions/20201115_115415.webp"&gt;
&lt;img alt="培訓課程的筆記 - 4" loading="lazy" src="https://assets.blog.pan93.com/toi-competition-opitions/20201115_115448.webp"&gt;&lt;/p&gt;
&lt;p&gt;培訓課程的筆記&lt;/p&gt;
&lt;h2 id="比賽的過程與收穫"&gt;比賽的過程與收穫&lt;/h2&gt;
&lt;p&gt;&lt;img alt="ya" loading="lazy" src="https://assets.blog.pan93.com/toi-competition-opitions/INFOP1.webp"&gt;&lt;/p&gt;
&lt;p&gt;雖然跟上次一樣，看不懂題目，或是看懂卻不會做，但其實我還是有一些額外的收穫。&lt;/p&gt;
&lt;p&gt;先說為何我「看懂卻不會做」，就例如「給三個點，求圍起來的三角形面積」。我知道三個點圍起是直角三角形的作法，但我不知道不規則三角形的解法。&lt;/p&gt;
&lt;p&gt;後來聽了講解之後，才知道要用的是海龍公式。真是遺憾，競賽時不能用網路，所以我沒辦法上網查「三個點求三角形面積」的公式，而且我也忘了海龍公式。只能說是數學基礎不夠好。&lt;/p&gt;
&lt;p&gt;還有下面這種「數字包數字」的題目，這題我至今還不會做，希望未來能看到能看懂的解法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;7 4444444
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;6 4333334
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;5 4322234
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;4 4321234
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;3 4322234
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2 4333334
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1 4444444
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;0 1234567
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這一題中，N=4，而我們會給四個數字，分別為 x1 y1 和 x2 y2。求 (x1, y1) 和 (x2, y2) 的值&lt;/p&gt;</description></item><item><title>VBA 筆記：選取事件</title><link>https://blog.pan93.com/posts/vba-selectionchange/</link><pubDate>Sun, 18 Oct 2020 23:40:43 +0800</pubDate><guid>https://blog.pan93.com/posts/vba-selectionchange/</guid><description>&lt;p&gt;有天我突然想寫一個 Excel 按鈕，但是又不想要做一個 Form，而想到最好的辦法，就是按 Excel 中的格子。那該怎麼做，才可以做出這種偽按鈕呢？&lt;/p&gt;
&lt;h2 id="原理思考"&gt;原理思考&lt;/h2&gt;
&lt;h3 id="excel-中按一下儲存格代表"&gt;Excel 中，按一下儲存格代表？&lt;/h3&gt;
&lt;p&gt;就是選取「單一」儲存格。就跟你拖曳選取一堆儲存格、選取整整一行或一列所代表的含意是相同的。&lt;/p&gt;
&lt;h3 id="那怎麼知道使用者選取了什麼"&gt;那怎麼知道使用者選取了什麼？&lt;/h3&gt;
&lt;p&gt;用 VBA 的事件 (Event)。只要使用者做了什麼動作（觸發），Excel 都會通知你發生新事件，告訴你使用者做了什麼。&lt;/p&gt;
&lt;p&gt;首先，Excel 遇到新事件時，會先看看你有沒有在指定的地方放下動作（Sub，子程式），如果沒放就等同忽略，而有放就會觸發（Trigger）這個動作。所以我們只要知道「選取儲存格後該在哪裡接收事件」，就完成了。&lt;/p&gt;
&lt;h2 id="正文"&gt;正文&lt;/h2&gt;
&lt;h3 id="建立接收事件的子程式"&gt;建立接收事件的子程式&lt;/h3&gt;
&lt;p&gt;VBA 中有一個跟儲存格變更有關的事件，叫做 &lt;code&gt;Worksheet_SelectionChange&lt;/code&gt;，長得像這樣：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-vb" data-lang="vb"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Sub&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Worksheet_SelectionChange&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;ByVal&lt;/span&gt; Target &lt;span style="color:#f92672"&gt;As&lt;/span&gt; Range)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;別急著複製。VBA 其實有提供一個非常簡單的事件選取工具。首先開啟 VBA 編輯器，找到你想要監聽的工作表，然後按一下上方的 [(一般)]，之後選擇 [Worksheet]。&lt;/p&gt;
&lt;p&gt;&lt;img alt="按一下「專案」中的其中一個工作表之後，在彈出來的畫面中按一下「(一般)」，之後按下「Worksheet」。" loading="lazy" src="https://assets.blog.pan93.com/vba-selectionchange/image-5.webp"&gt;&lt;/p&gt;
&lt;p&gt;選擇 Worksheet&lt;/p&gt;
&lt;p&gt;然後選擇 [SelectionChange]，搞定！&lt;/p&gt;
&lt;p&gt;&lt;img alt="接著，找到旁邊的下拉框，選擇「SelectionChange」。" loading="lazy" src="https://assets.blog.pan93.com/vba-selectionchange/image-6.webp"&gt;&lt;/p&gt;
&lt;p&gt;選擇 SelectionChange&lt;/p&gt;
&lt;h3 id="了解子程式的結構"&gt;了解子程式的結構&lt;/h3&gt;
&lt;p&gt;首先，我們來看看產生的程式碼。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-vb" data-lang="vb"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Sub&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Worksheet_SelectionChange&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;ByVal&lt;/span&gt; Target &lt;span style="color:#f92672"&gt;As&lt;/span&gt; Range)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;End&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Sub&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;你會發現到裡面出現了非常多奇怪的東西。什麼是 &lt;code&gt;Private&lt;/code&gt; 和 &lt;code&gt;ByVal&lt;/code&gt;？&lt;code&gt;Target&lt;/code&gt; 是什麼？&lt;code&gt;As Range&lt;/code&gt; 又是什麼東西？&lt;/p&gt;
&lt;p&gt;首先，每個工作表都有自己獨立的事件，也就是你在 A 工作表選取東西，不干 B 工作表的事。如果用 &lt;code&gt;Public&lt;/code&gt;，就代表 A 工作表的事件也會影響其他工作表。這不應該發生。而 &lt;code&gt;Private&lt;/code&gt; 就可以限定這個事件是 A 工作表 only 的。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;ByVal&lt;/code&gt; 則需要一點資料結構的知識。VBA 傳遞參數有兩種形式，一個是 &lt;code&gt;ByVal&lt;/code&gt;，建立物件的副本，另一個則是 &lt;code&gt;ByRef&lt;/code&gt;，傳遞指向原物件的參考。有興趣可以看看〈&lt;a href="https://www.lanrenexcel.com/vba-byval-byref-basic/"&gt;VBA 中 ByVal 和 ByRef 的基础用法和区别&lt;/a&gt;〉。&lt;/p&gt;</description></item><item><title>淺談翻譯弊病 (1)</title><link>https://blog.pan93.com/posts/translating-evil-1/</link><pubDate>Fri, 16 Oct 2020 17:05:02 +0000</pubDate><guid>https://blog.pan93.com/posts/translating-evil-1/</guid><description>&lt;p&gt;「Install this software to your computer.」&lt;/p&gt;
&lt;p&gt;這句話你會怎麼翻？一些保守翻譯者會翻成「安裝此軟體至您的電腦。」，但我覺得這樣翻不好，為什麼？&lt;/p&gt;
&lt;h2 id="翻譯是給人看的"&gt;翻譯是給人看的&lt;/h2&gt;
&lt;p&gt;拘泥於逐字翻譯，會使翻譯不近人情。就以我上面提的翻譯來說，你平常說話並不會說成這樣，即便是書寫，也不會這樣寫。&lt;/p&gt;
&lt;p&gt;這就算是典型的&lt;strong&gt;翻譯腔&lt;/strong&gt; － 你沒翻錯，但很怪。一個優秀的翻譯，不會讓人覺得突兀，就像是母語使用者說的。&lt;/p&gt;
&lt;p&gt;就我自己的翻譯習慣，我會先念一遍翻譯，並尋找奇怪的地方。就以開頭那句，我會翻成「&lt;strong&gt;將軟體安裝進您的電腦&lt;/strong&gt;」，更好讀但又不會太口語化。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tw.blog.voicetube.com/archives/19126/%E9%80%99%E5%8F%A5%E4%B8%AD%E6%96%87%E6%80%8E%E9%BA%BC%E7%BF%BB%E5%BE%97%E9%82%A3%E9%BA%BC%E5%BD%86%E6%89%AD%EF%BC%81%E5%8D%81%E5%A4%A7%E5%B8%B8%E8%A6%8B%E7%BF%BB%E8%AD%AF%E8%85%94%E4%BD%A0%E9%83%BD/"&gt;如果有興趣可以看看這篇翻譯腔相關的文章&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="翻譯一點都不簡單"&gt;翻譯一點都不簡單&lt;/h2&gt;
&lt;p&gt;也因此，要翻出一個好的翻譯其實不是個簡單的事情。如果翻譯只會逐字翻，那我大可可以直接用 Google 翻譯或 DeepL 之類的服務。翻譯更需要的是理解後，&lt;strong&gt;在不破壞原意的情況下的重新表達&lt;/strong&gt;。這需要的不僅僅是翻譯員的語文能力，以及翻譯的經驗。&lt;/p&gt;</description></item><item><title>關於</title><link>https://blog.pan93.com/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://blog.pan93.com/about/</guid><description>&lt;p&gt;目前是 &lt;a href="https://zeabur.com"&gt;Zeabur&lt;/a&gt; 的工程師，也是一個大四學生。&lt;/p&gt;
&lt;p&gt;主要是寫 code、翻譯和水群的，偶爾也會設計跟發一些廢文。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pan93.com/?utm_source=blog&amp;amp;utm_medium=about"&gt;比較完整的自我介紹 →&lt;/a&gt;&lt;/p&gt;</description></item></channel></rss>