Author Avatar

Raiven Kao

a piece of me

Container Image Optimization 那些年我們寫錯的 Dockerfile

最近在檢視公司內部的專案時,針對其中一個 container image 進行了優化。在一個簡單的 commit 後,我們的 image size 從 1.82GB 修正到了 1.18GB。 透過 dive 查看,Image Efficiency 更是從不及格的 69% 飆升到了 99%。 這讓我回想起過去寫 Dockerfile 時,常常因為不了解 Docker Layer 的機制,或是為了寫起來「方便」,而踩到了許多效能與安全的地雷。 致命的 chown 這次優化的核心,其實源自於一個非常常見的操作:修改檔案權限。 在我們的案例中,Dockerfile 原本是這樣寫的: # Bad Practice: recursive chown after copy FROM ubuntu:22.04 WORKDIR /app COPY . . # ... install dependencies ... RUN dpkg -i packages/*.deb # Change ownership for security reasons RUN groupadd -r appuser && useradd -r -g appuser appuser RUN chown -R appuser:appuser /app 看起來邏輯很正確:把檔案複製進去,安裝套件,最後為了安全性將檔案權限交給非 root 使用者。 ...

2026-01-14 · 3 min · 506 words

Golang 1.26 新特性在數量上史無前例的多

隨著時間來到 2026 年初,Go 語言迎來了 1.26 版本的更新。如果說 Go 1.18 的泛型是語言層面的重大變革,那麼 Go 1.26 則是在「數量」與「廣度」上讓人感到驚艷的一次釋出。從語言特性的語法糖、標準庫的實用擴充,到 Runtime 效能的顯著提升(Green Tea GC),甚至是實驗性的 SIMD 支援,這次的更新內容豐富到讓人目不暇給。 本文將挑選其中幾個我認為對日常開發最重要、或最有趣的改動來進行介紹。 語言層面的改動 new(expr):終於不用再寫輔助函數了 在 Go 1.26 之前,如果我們想要取得一個基本型別(如 int, bool, string)的 pointer,通常需要宣告一個變數或者寫一個輔助函數。這在定義 struct 的 literal 時特別煩人,尤其是當 struct 欄位是 *bool 或 *int 用來區分「零值」與「未設定」的時候。 回顧過去,我們為了這個小需求付出了不少努力: 在 Go 1.18 泛型出現之前:我們經常需要定義一堆如 Int64Ptr(v int64) *int64 或 Float64Ptr(v float64) *float64 的輔助函數(AWS SDK 的使用者應該對此非常熟悉)。 匿名函數大法:如果不想要定義全域的輔助函數,有時甚至會看到像 enabled := func(b bool) *bool { return &b }(true) 這種冗長且難讀的寫法。 泛型時代:雖然可以用一個通用的 ptr[T] 解決,但還是需要額外的程式碼。 以前我們可能需要這樣做: func ptr[T any](v T) *T { return &v } type Config struct { Enabled *bool } conf := Config{ Enabled: ptr(true), } 在 Go 1.26 中,內建的 new 函數得到了增強,現在它不僅接受型別,還可以直接接受表達式(Expression)。 ...

2026-01-13 · 3 min · 536 words

從 GrapheneOS 回歸原廠:Google Pixel 5 刷機紀錄

最近整理抽屜時,翻出了這台陪伴我一段時間的 Google Pixel 5。 身為一個喜歡折騰的開發者,早在它退役成為備用機時,我就第一時間把它刷成了 GrapheneOS。不得不說,GrapheneOS 在隱私保護和安全性上做得真的很好,沒有 Google Play Services 的純淨體驗也別有一番風味(雖然依賴 Sandboxed Google Play 還是能解決大部分 App 的問題)。 但就在前幾天,或許單純是想念 Pixel 原生系統的那些獨家功能(原生相機等等),我決定讓它「回歸原廠」。 原本以為又要經歷一番 adb 和 fastboot 的指令轟炸,還要自己去 Google 官網下載好幾 GB 的 Factory Image 壓縮檔,解壓後再祈禱 flash-all.sh 不要噴錯。 沒想到現在 Google 官方提供了一個方便的網頁工具:Android Flash Tool。 這篇文章簡單紀錄一下如何從 GrapheneOS 這種 Custom ROM,透過官方工具刷回 Stock Firmware 的過程。 為什麼選擇 Android Flash Tool? 以前刷機的標準作業流程(SOP): 搜尋正確的 Factory Image(還要對型號,買到電信商鎖定版就哭哭)。 下載 Android SDK Platform-Tools。 解鎖 Bootloader。 執行 flash-all script。 遇到驅動程式問題、傳輸線問題、路徑問題… 現在的 Android Flash Tool 直接把這些步驟簡化了: WebUSB: 直接透過瀏覽器與手機溝通,不用煩惱太底層的驅動設定(至少在 macOS/Linux 上是這樣)。 Auto Detect: 自動偵測裝置型號,自動下載對應的最新版韌體。 Fool-proof: 圖形化介面引導你開啟 USB Debugging 和 OEM Unlocking。 事前準備 雖然工具很強大,但基本的準備還是要有的: ...

2026-01-10 · 2 min · 370 words
2025 Recap

2025 Recap

當兩個人意見不一致,可能其中一個人是錯的。如錯錯的人是你,難道你不想知道嗎? 出自 Ray Dalio 的《原則》一書,是我 2025 年對自己的提問。 接續 2023 年展望,與 2024 Recap,用這篇文章來紀錄今年的自己。 可以搭配今年度最常聽的音樂 Again (Yui Acoustic Version) 工作 去年把工作放到最後,今年則是放到第一位。今年換到了新的公司、新的工作崗位,從十幾位 RD 的新創公司,換到了有一千多位 RD 的大公司。即便已經沒有過去擔憂的「過度官僚」文化,但仍在職也不好多說什麼。 整體來說目前待的團隊,是夾雜著無趣卻也有趣的產品,無趣的點在於產品本身並不性感,目標客群是企業客戶,幾乎沒有什麼創新的概念。有趣的點在於,如何在有限的時間內拓展產品線,並且以 UX 的角度來思考企業客戶如何使用產品,並花費更多時間在前期的 design,而不是單就功能進行開發,時常會需要花費八成的時間在前期規劃,真正的開發時間就是靠 AI(LLM) 解決。 隨著團隊拓展,我也承擔了 mentor 的責任,第一次帶新人多少有些徬徨與無助。隨著團隊擴展,漸漸的開始反思現代軟體開發,一件一個人能完成的事情被拆成四五個人都要有貢獻,導致交付速度不升反降,這樣真的是正確的嗎?我想還需要更多的時間積累才能找到答案。 不得不提的就是,目前的公司有「近乎無限」的 AI 資源(特別指 LLM),讓手速不再成為寫程式的瓶頸。與前東家不同的是,這間公司是在今年才開始轉型 AI First,並且透過 sharing & AI Hackathon 來推廣 AI 在內部開發與產品上的使用。不過觀察下來,年初的 AI 使用率應該是不高的,就連我這個 AI 質疑論者,都能被邀請去 All Hands 進行 AI 使用的 session。 健康 今年在健康上只能說完全荒廢,與去年底期許的目標,不僅完全沒有維持,更糟糕的是因為通勤距離變遠了,導致運動時間大幅下降。 但… 我想這一切都只是藉口,單純只是變懶惰、沒了動力。只能把希望寄託在 2026 年,能透過「12周做完一年工作」這本書內提到的方法,不設定目標,而是設定行動,先把行動給完成再看看怎麼推進了。 旅遊 隨著離開新創公司,又回到了下班不認識的那個自己,今年多數旅遊都是跟 Murphy 兩人,連爬山也變少了,希望 2026 年能投入更多時間在旅遊上。 ...

2025-12-28 · 1 min · 206 words

Helm Smart Resource:讓你的 Chart 學會與既有資源和平共處

前言 在 Kubernetes 的世界裡,Helm 無疑是管理應用程式部署的霸主。它標準化了資源的定義,讓我們可以用宣告式的方式管理整套系統。然而,在真實世界的維運場景中,事情往往沒那麼單純。 我們常遇到一種尷尬的情況:某些資源(例如 Database 的 Secret、外部系統的 ConfigMap)可能在 Helm Chart 安裝之前就已經由維運人員手動建立,或是由另一個流程(如 Terraform)預先準備好了。 這時候,如果直接執行 helm install,往往會收到 “resource already exists” 的錯誤;如果使用 helm upgrade --install,又擔心 Helm 會覆蓋掉這些既有設定。 這篇文章將分享一種「Smart Resource」的設計模式,透過 Helm 的 lookup 函數與樣板邏輯,讓你的 Chart 能夠聰明地判斷:「這東西是我管的嗎?如果是,我才動它;如果不是,我就尊重現狀。」 核心難題:Ownership 在 Kubernetes 中,資源的「所有權」觀念至關重要。Helm 預設認為它 release 中的所有資源都應該由它全權管理。但當我們需要與外部資源協作時,我們需要更細緻的控制。 我們的目標很明確: 若資源不存在:建立它,並標記為 Helm 管理。 若資源已存在且由 Helm 管理:更新它(Patch/Merge)。 若資源已存在但由外部管理:保持原狀,不進行覆蓋或刪除。 為了達成這個目標,我們需要一個輔助函數來判斷資源的歸屬權。 實作細節 1. 定義所有權檢查 首先,我們在 _helpers.tpl 中定義一個檢查函數。Helm 會在它建立的資源上打上特定的 Annotations(meta.helm.sh/release-name 和 meta.helm.sh/release-namespace)。我們可以利用這一點來判斷資源是否屬於當前的 Release。 {{/* Check if a resource is owned by this Helm release Returns "true" or "false" as string for stable piping */}} {{- define "visionone-filesecurity.isOwnedByRelease" -}} {{- $resource := .resource -}} {{- $releaseName := .releaseName -}} {{- $releaseNamespace := .releaseNamespace -}} {{- $owned := and $resource (hasKey $resource.metadata "annotations") (eq (get $resource.metadata.annotations "meta.helm.sh/release-name") $releaseName) (eq (get $resource.metadata.annotations "meta.helm.sh/release-namespace") $releaseNamespace) -}} {{ printf "%t" $owned }} {{- end -}} 這段程式碼邏輯很簡單:只有當資源存在,且其 Annotations 中的 Release Name 與 Namespace 都與當前 Release 相符時,才視為「Owned」。 ...

2025-11-22 · 4 min · 715 words

Interface 不是有開就好:從一個 PR 來看抽象化的重要性

前言 最近團隊正在開發一個新產品,其中一個核心功能需要 client 與 server 之間進行即時、雙向的溝通。經過一番技術評估,我們決定採用 WebSocket 來實現這個需求。 身為一個良好習慣的開發團隊,我們在開發初期就導入了依賴注入(Dependency Injection),希望透過界面(Interface)來解耦商業邏輯與具體的實作,這樣不僅能提高程式碼的可測試性,未來在更換底層實作時也能更加輕鬆。 一切聽起來都很美好,直到我在一次 Code Review 中,看到了一段熟悉的程式碼。 一個 PR 的故事 在我們的 Domain Layer,也就是處理核心商業邏輯的地方,我看到同事定義了下面這個 interface: // package/to/domain/service.go // WebSocketService defines the interface for websocket communication. type WebSocketService interface { // StartAndLinsten starts the service and listens for incoming messages. StartAndLinsten(ctx context.Context) error // Send sends a message to the client. Send(ctx context.Context, message any) error // ... other methods } 第一眼看過去,好像沒什麼大問題。有名稱、有方法、也確實是個 interface。然而,當我細看 WebSocketService 這個命名時,總覺得哪裡怪怪的。 於是我在 PR 上留下了這樣的 comment: 這個界面主要是抽象化 client 與 server 間的互動,不應該侷限於 WebSocket 這個 Protocol。假如我們未來要換成使用 socket.io 或是 gRPC stream,是不是連 domain 層的 interface 也要跟著改動? ...

2025-10-04 · 2 min · 334 words
監控你的執行檔:初探 watchexec

監控你的執行檔:初探 watchexec

前端開發有 liveserver,後端開發有 air,那 TUI 開發呢?本文記錄了我在開發 Bubbletea 應用時,從 air 轉向 watchexec 的心路歷程,以及如何使用這個通用工具來優雅地實現終端機應用的熱重載。

2025-09-06 · 2 min · 237 words

用 Golang Bubbletea 打造終端機應用:從 Hello World 到多頁面架構

探索如何使用 Golang 的 Bubbletea 函式庫,基於 Elm 架構,從零開始打造一個互動式終端機應用(TUI)。本文將從一個簡單的計數器範例,逐步引導你建構出一個類似 Web 應用的多頁面架構,並分享整個生命週期中的關鍵概念與注意事項。

2025-09-05 · 3 min · 630 words

初探 Tauri:為了解決朋友的繁瑣任務,寫了個桌面 App

一個非工程師朋友的日常繁瑣任務,成為我初探 Tauri 的契機。本文記錄如何使用 Rust 的強大後端結合 React 的靈活前端,打造一個輕量、跨平台的桌面應用,來解決真實世界的問題。

2025-08-27 · 2 min · 352 words
Prompt to Product:AI 時代開發者的範式轉移

Prompt to Product:AI 時代開發者的範式轉移

那些年我們追逐的開發效率 還記得第一次聽到「十倍工程師」這個詞的時候,心中總是充滿憧憬。想像著有一天也能寫出十倍的程式碼、處理十倍的任務、創造十倍的價值。每當看到某個同事在短時間內完成複雜的功能,總是會想:他們是不是真的擁有某種神秘的超能力? 隨著經驗的累積,漸漸發現這更像是一個美麗的神話。真正的效率並不在於敲擊鍵盤的速度,也不在於記住多少語法細節。那些看似神奇的十倍效率,往往來自於對問題本質的深刻理解,以及選擇正確工具的智慧。 直到 ChatGPT 橫空出世,Agentic AI 開始進入我們的開發流程,我開始思考:AI 能為我們帶來什麼?又有什麼是它永遠無法取代的? 從懷疑到接受的心路歷程 最初的抗拒與恐懼 坦白說,剛開始接觸這些 AI 工具時,我的內心是抗拒的。作為一個有著多年開發經驗的工程師,看著這些「會寫程式的機器」,心中五味雜陳。那種感覺就像是突然有人告訴你,你引以為傲的專業技能可能會被一個沒有情感的演算法取代。 第一次使用 AI code assistant 時,我提出一個簡單的需求:「幫我寫一個排序函數」。當它瞬間給出了完整且正確的程式碼時,我的第一反應不是驚喜,而是擔憂。這些工具真的實用嗎?會不會最終取代我們這些工程師?依賴 AI 是否會讓我們的技能退化? 這些疑慮很真實,也很合理。畢竟,我們這個行業向來對「銀彈」保持謹慎態度。太多次,我們看到某個新技術被吹捧為「革命性的」,最終卻發現它只是解決了某個特定問題,而創造了十個新問題。 重新定義效率的數學陷阱 然而,在實際使用 Zed AI、GitHub Copilot Agent 等工具幾個月後,我發現自己的思維框架需要調整。「十倍工程師」的概念確實存在一個隱藏的數學陷阱,而理解這個陷阱是擁抱 AI 輔助開發的第一步。 我開始仔細觀察 AI 在哪些環節能夠提供幫助。當我需要寫一個複雜的正則表達式時,AI 能在幾秒鐘內給出準確的答案,而我可能需要查資料和測試十幾分鐘。當我需要重構一個大型函數時,AI 能夠快速理解程式碼邏輯並提供多種重構方案。當我需要撰寫技術文件時,AI 能夠幫我整理思路,甚至提供不同角度的表達方式。 但同時,我也清楚地感受到 AI 的局限。當系統編譯時,AI 無法讓 CPU 運算得更快。當測試套件在 CI 環境中執行時,AI 無法讓網路延遲消失。當我需要與產品經理討論需求細節時,AI 無法代替我進行那些微妙的人際溝通。當面對複雜的架構選擇時,AI 可以提供選項,但最終的決策仍需要結合業務上下文和長期策略考量。 有一天,一位同事興奮地告訴我:「用了 AI 工具後,我覺得自己的開發速度提升了十倍!」我問他:「你的專案交付時間真的縮短了十倍嗎?」他想了想,搖搖頭說:「沒有,可能只快了兩三倍。」這就是數學陷阱的本質:AI 能夠大幅加速某些特定環節,但整個開發流程的速度受限於最慢的那個環節。 從工具使用者到架構思考者 這種認知的轉變讓我重新審視自己作為開發者的角色定位。AI 的出現並沒有降低我們的價值,反而讓我們從機械性的程式碼編寫中解放出來,有更多時間專注於真正重要的事情:理解業務需求的本質、設計優雅的系統架構、做出明智的技術選型決策。 我開始意識到,真正的「十倍效率」不是來自於打字速度的提升,而是來自於思維方式的轉變。當 AI 幫我們處理了大量重複性工作後,我們有機會成為更好的架構師、更好的問題解決者、更好的技術領導者。 機器可讀設計的哲學革命 經過這段時間的實踐,我得出了一個看似簡單但意義深遠的核心理念:「只有機器可讀的設計,才能被 LLM 加速」。 重新審視我們的設計習慣 這個理念讓我開始重新審視過去的工作習慣。回想一下我們是如何管理專案的:每次設計評審,我們會花費大量時間製作精美的 PowerPoint,其中包含各種流程圖、架構圖、時序圖。這些圖表在投影幕上看起來很專業,但會議結束後,它們就被儲存為 PNG 或 PDF 格式,靜靜地躺在某個共享資料夾中,再也沒有人會去更新或維護。 ...

2025-08-10 · 3 min · 470 words