建造 OurCar:關於 UX、狀態管理與說不的藝術的教訓
與家人共用車輛在油箱耗盡之前常看起來很簡單。對於 Mendel Greenberg,分擔燃油成本和協調時間表的摩擦成為了一個技術專案的催化劑:OurCar。最初為了避免「千刀萬剐」的油費賬單而展開的探索,逐漸演變為對跨平台開發、使用者體驗(UX)設計以及維護小眾應用程式現實的深入研究。
問題:協調摩擦
核心問題是一種典型的協調失敗。雖然家人在長途旅行後試圖加油,但小事仍會讓油箱慢慢耗盡。結果是,當車子最終耗盡時,誰剛好開車誰就要承擔不公平的負擔。
Greenberg 的目標是建造一個「比 WhatsApp 群組更適合共用車輛」的東西。這需要三項主要功能:
- 位置追蹤: 知道車子在可用時停在哪裡。
- 狀態監控: 追蹤誰在使用車子以及何時使用。
- 燃油會計: 監控燃油使用量以決定誰應該補油。
技術棧
為了從原型快速過渡到生產,Greenberg 選擇了精簡的技術棧:
- 前端: Flutter 以達到跨平台覆蓋。
- 後端: Pocketbase 提供簡化的後端即服務體驗。
- 狀態管理: Riverpod 用於跨多頁同步資料。
- 導航: Auto Route 處理深層連結和複雜路由。
雖然像 Claude Code 這樣的 agentic 開發工具被用於故障排除和建構次要功能(例如帳號刪除入口),但核心架構是手動處理的,以確保對系統的深入理解。
「原生」設計的挑戰
最重大的障礙之一是在 iOS 和 Android 上實現「原生感覺」。Flutter 提供了 Material(Android)和 Cupertino (iOS) 設計系統的小工具,但它不會自動在它們之間切換。
為了解決這個問題,Greenberg 使用了 flutter_platform_widgets 套件,儘管他指出佈局慣用法在平台之間仍有顯著差異,這常導致為了處理細節而出現「義大利麵」程式碼。他發現設計 UI 最有效的方法是參考已經掌握跨平台平衡的應用程式,具體來說是 WhatsApp。
"如果你遵循標準慣用法,人們會突然明白它是如何運作的,因為他們以前多次遇過這種互動。"
範圍擴張與說不的藝術
當應用程式觸及家人時,請求紛紛而至:維護時間表、用於里程表掃描的電腦視覺,以及即時 GPS 追蹤。這迫使開發者進行關於「反功能」的關鍵決策過程。
以 GPS 追蹤為例,因兩個原因被拒絕:電池耗損和隱私。雖然應用程式需要知道車子停在哪裡,但追蹤每一次移動會揭露使用者生活的私密細節。透過將這些視為反功能,開發者在不犧牲使用者信任或裝置效能的情況下維持了應用程式的核心實用性。
工程陷阱與迭代
狀態與效能
早期版本遭遇「卡頓」和同步問題。開發者發現建立「巨型小工具」會導致過度重繪,因為父小工具的狀態微小變化會觸發所有後代的重繪。解決方案是進行嚴格的重構,將其拆分為更小、更易管理且狀態隔離的小工具。
狀態機方法
最初,燃油記錄和行程記錄是分開的,這讓使用者感到困惑。Greenberg 轉向單一統一的時間軸,作為狀態機運作。邏輯變為二元:車子在被歸還時可以被取走,而在被取走時可以被歸還。
回顧此事,他指出,一個更「抽象」的狀態機——其中邏輯與資料儲存方式無關——將有助於更好的單元測試和更少的錯誤。
社群觀點:過度工程 vs. 實用性
此專案在 Hacker News 上引發了關於此類應用程式必要性的討論。一些批評者認為鉛筆和筆記本或簡單的 PWA(Progressive Web App)就足夠了,將此專案稱為「過度工程」的範例。
然而,其他人則反駁說,專用應用程式的價值不僅在於資料記錄,而在於它所強制執行的 工作流程。正如一位評論者所指出的:
"一個粗糙但人人實際開啟的 UI 可以勝過由一人維護的整潔紙本記錄……一個專門建造的助手可以成為工作流程,而不僅僅是其記錄。"
其他技術建議包括整合 OBD-II 藍牙動鈴,以自動化燃油和里程表讀數,完全消除手動資料輸入的摩擦。
結論:小眾應用程式的生命週期
最終,OurCar 長期處於「永遠的 alpha」階段。隨著家人搬離,應用程式的即時需求消失,進一步開發的熱情也隨之枯竭。儘管如此,此專案仍是一次完整的軟體開發生命週期練習:從識別痛點和製作原型,到應對應用商店公證的複雜性以及反覆的 UX 設計。