TypeScript monorepo 架構圖——微服務共享單一 repo

整個團隊都沒做過的那次遷移

背景 Shopback 的後端跑在一組微服務上——每個服務各自管理一個領域,透過網路溝通,各自擴縮容。這個架構有清楚的好處,也有顯而易見的成本:閒置的 container 仍然佔用分配的資源,就算當下沒在處理任何流量,機器費照算。 遷移到 monorepo 架構的動機之一就在這裡。TypeScript monorepo 讓多個服務可以共享同一個 process 和資源池,閒置的開銷縮小,機器費也跟著縮小。 我的子任務很具體:兩個服務原本透過網路互相呼叫 API,遷移之後改為在 monorepo 裡直接呼叫函式。聽起來是個重構。實際做起來,變成我實習期間最困難的一件事。 為什麼這件事很難 第一層困難是技術面。Shopback 的 TypeScript monorepo 結構主要是為 library 和共用套件設計的——這是 monorepo 最常見的使用情境。在這個結構裡跑真正的後端服務是另一回事。服務有自己的啟動生命週期、自己的 framework 相依、自己的 runtime 問題。Library 沒有這些。 團隊裡已經有工程師實作了部分 monorepo,但針對的是 library 使用情境。他們的程式碼我看得到,但沒辦法直接套用——他們設計時的假設對 running services 不成立,尤其是不同後端 framework(我們跨服務用了不只一種)在 monorepo 的依賴解析機制下如何互動。 還有版本相容性的問題。TypeScript monorepo 在 workspace 層級管理套件版本,而某些跨服務依賴有寫死的版本假設,合在一起跑就會出現衝突。這不是能用 grep 找到的 bug,而是跑起來才會炸、而且 stack trace 看不出所以然的那種問題。 Mentor 也沒有答案 第二層困難是:我的 mentor 也沒做過這件事。 這不是批評,就是事實。他有自己的工作,能幫的地方他都幫了。但針對我在解的這個遷移問題,他跟我一樣是從零開始。 所以我在一個技術問題上獨立工作,沒有團隊裡的前例,codebase 夠大、搞清楚相關部分要花真正的時間,而 framework 設定在內部也缺乏文件記錄。 兩三個 Sprint,我進度緩慢。我能看出需要做什麼,但真的讓實作跑起來是另一回事。 那條 Slack 訊息 最後我做了一件我一直猶豫要不要做的事:主動傳訊息給另一個團隊的資深工程師,他是我知道曾碰過 codebase 相鄰部分的人。 ...

November 1, 2025 · 1 分鐘 · Yi-Wei Lien
Shopback app 選擇面板 — 列表中的商家圖示

讓我重新理解「有影響力」的那個小功能

在 Shopback 有一個功能,我當時幾乎沒多想。 需求很單純:PM 有一個 app 內選擇面板的設計——一個讓用戶從列表中選取項目的小 UI 元件。設計稿上要在每個項目旁加上商家圖示。我的部分是跟前端團隊協作,確保後端整合正確接上。沒有什麼技術難度,純粹是協調工作:PM 有想法,設計有 spec,我幫忙把兩端串起來讓功能可以上線。 就這樣。 上線後,stakeholder 分享了數據。那個面板的進入率——看到頁面後實際點進選擇面板的用戶比例——從大約 10% 跳到 40% 以上,提升超過 30%。 我記得看到這個數字的時候,停頓了一下。 我原本隱隱持有的模型 在這之前,我對軟體工作的影響力有一個安靜的假設:影響力跟難度成正比。問題越難,就越重要。系統遷移、效能重構、不直觀的演算法——那才是「真正的工作」。UI 調整?清單上加幾個圖示?那幾乎算不上工程。 但數據不同意。 那個面板的改動影響了大量用戶的操作。它改變了人們打開 app 之後做的事。而我花了更多時間做的那些工作——跨服務 API 遷移、錯誤率降低——固然重要,但它們對用戶的可見影響是比較安靜的。基礎設施工作通常都是如此。 圖示是半天的協調工作,換來 30% 的行為轉變。 為什麼好設計是真正的工作 圖示真正做到的事,是給面板裡每個項目一個身份。加圖示之前,列表項目大概只是文字或通用條目。加了之後,每一行都有視覺錨點——一個在說「這是一個真實的東西、一個可辨識的東西、值得你注意的東西」的信號。用戶對這個信號做出了回應。 這不是新觀念。但「知道設計很重要」和「看著設計在一個你指得出來的指標上發揮作用」,是兩件不同的事。 我更新的東西是「工程貢獻」的範圍。讓後端整合正確接上,讓圖示在對的時間載入,又不破壞面板原本的行為——這是真實的貢獻。不性感,但它是讓 PM 願景可以上線的那個東西,而能上線的東西是推動數字的那個東西。 關於驕傲這件事 有一件事我沒有完全預期到:看到數字的時候,我的感受是什麼。 那種驕傲不是來自技術難度,而是來自效果。我碰過的東西改變了大量用戶打開 app 之後的行為。這和乾淨解決一個困難問題的滿足感不太一樣——沒那麼智識性,但更連接到真實的人。 我覺得這是軟體業值得追求的那種驕傲形式。不是「我解決了一件很難的事」,而是「我做的東西現在已經是某些人移動世界方式的一部分了」。即使那個移動很小——多點了一個按鈕,看到了之前沒看到的東西。 有影響力的工作不一定是房間裡最難的問題。有時候,它看起來就像一排圖示。

October 10, 2025 · 1 分鐘 · Yi-Wei Lien
Bubblo — 2D 像素平台遊戲標題畫面

一學期帶一個遊戲專案:敏捷沒有教你的事

背景 2025 年 4 月。六個人,一個學期,目標:用 Unity 完成一款完整的 2D 平台遊戲。 這款遊戲叫 Bubblo。你扮演一隻泡泡生物,在像素風格的奇幻樂園中探索,拯救被關在籠子裡的村民,同時對抗各種針型敵人——蜜蜂、跳躍蜘蛛、還有一隻非常想把你戳破的獨角獸。核心機制是泡泡物理:在關卡中彈跳漂浮,用你的柔軟特性同時解決移動和戰鬥問題。 我是專案負責人。我的工作是把一堆模糊的想法轉化為真正可以交付的遊戲細節——然後讓另外五個人在三個月內朝同一個方向前進,而且沒有人中途放棄。 我從 Cmoney 帶來了什麼 在這之前幾個月,我剛結束在 Cmoney 的後端實習,九個月的真實敏捷流程:衝刺規劃、每日站立、Sprint Review、回顧。我參加了夠多這些會議,對它們的用途有個淺層的理解。 所以開始 Bubblo 時,我有一套框架。我們會跑兩週的 Sprint,有正式的任務看板,做 Retrospective。我知道這些詞彙。 但我還不完全理解的是:這些詞彙背後有一套隱含的假設——關於人們如何安排時間、「可用性」代表什麼,以及一個團隊最主要的義務是什麼。 在公司,每個人最主要的工作就是這個專案。Sprint 是容器。敏捷儀式有效,是因為它設計的前提是全職投入。 在學校,這對任何人都不成立。 為什麼儀式不管用 我們的團隊成員有實驗室工作、其他課程、課外活動、實習申請要處理。有人在 Sprint 中途趕一篇研究論文;有人有兼職工作。每個人都是一個完整的人,在 Bubblo 之外有完整的生活。 要跑一場完整的 Sprint 規劃,意味著請人們拿出他們根本沒有的兩個小時。開回顧會議感覺很表演,因為真正的阻礙只是大家很忙、而且對此感到愧疚。每日站立變成焦慮的來源,而不是同步機制。 僵硬的結構在製造摩擦,而不是消除摩擦。 設計一個真正合適的工作流程 所以我把它拆解精簡。 任務看板留下來——這是最有價值的東西。所有人都能非同步看到「目前有什麼任務、什麼在進行中、什麼被卡住了」。維護這個看板不需要任何儀式,成本幾乎是零。 我用非同步確認取代了排程站立:每次工作時段開始時發一條短訊息,沒有格式要求,只是「今天在做 X,如果 Y 沒解決可能會卡住」。低壓力,大家真的會看。 每週同步變成一場有單一議程的會議:現在誰被什麼卡住了,我能在接下來三十分鐘內幫你解決嗎? 不是進度報告,不是狀態更新。只有阻礙。跑二十到三十分鐘,阻礙清完就結束。 背後的原則是:尊重每個人的第一義務不是這個專案,但讓人們在有時間的時候容易貢獻。精簡,但不鬆散。 沒有人想要的 OOP 重構(但我們還是做了) 大約六週後,我們遇到了問題。角色行為的程式碼已經有機生長成一團亂麻。多個人在碰同一塊,一個地方的改動會在另一個地方產生難以追蹤的 bug。 我們重構了。Sprint 進行中。學生時程上。 在任何遊戲程式碼開始之前,我已經在紙上把元件架構設計成圍繞 Observer、State Machine 和 Command 模式。重構是要在實作中真正落地這些模式:讓狀態轉換變得明確、讓事件系統成為跨元件溝通的唯一真相來源。 這是正確的決定。重構之後,新增一種敵人類型或玩家狀態變成了一個有邊界的操作。Sprint 最後三分之一明顯比前三分之二不混亂。 時機感覺不對,但繼續修補一個纏繞的 codebase 會更糟。有些技術債複利速度夠快,必須提早還清。 AI 工具的時代背景 整個專案我們用 ChatGPT。那時候還沒有 IDE 代理工具——沒有整合的程式碼生成,頂多就是補全。主要用法是:描述一個問題,拿到一個草稿,再把草稿改成能用的東西。 ...

July 10, 2025 · 1 分鐘 · Yi-Wei Lien