构建 OurCar:UX、状态管理与说“不”的艺术

共享车辆在家庭成员之间看似简单,直到油箱耗尽。对于 Mendel Greenberg 来说,分摊燃油费用和协调时间表的摩擦成为了一个技术项目的催化剂:OurCar。最初只是为了避免在燃油账单上出现“千刀万剐”的困境,后来却演变成一次跨平台开发、用户体验(UX)设计以及维护小众应用的全方位探索。

问题:协调摩擦

核心问题是经典的协作失效。当家庭成员在长途旅行后尝试加油时,日常小跑却让油箱慢慢耗尽。结果是,最终油箱空了时,恰好开车的人承担了不公平的负担。

Greenberg 的目标是打造一个“比 WhatsApp 群组更适合共享汽车”的工具。这需要三个主要功能:

  1. Location Tracking(位置追踪): 知道车辆在可用时停放在哪里。
  2. Status Monitoring(状态监控): 跟踪谁在使用车辆以及使用时间。
  3. Fuel Accounting(燃油结算): 监控油耗,以确定谁需要加油。

技术栈

为了快速从原型转向生产,Greenberg 选择了精简的技术栈:

  • Frontend(前端): Flutter 用于跨平台覆盖。
  • Backend(后端): Pocketbase 提供简化的后端即服务体验。
  • State Management(状态管理): Riverpod 用于在多个页面之间同步数据。
  • Navigation(导航): Auto Route 处理深度链接和复杂路由。

虽然像 Claude Code 这样的智能开发工具被用于排查问题和构建次要功能(例如账户删除门户),但核心架构仍手动实现,以确保对系统有深入的理解。

“原生”设计的挑战

最具挑战性的难题之一是让 iOS 与 Android 都拥有“原生感”。Flutter 为 Material(Android)和 Cupertino(iOS)设计系统提供了对应的组件,但不会自动在两者之间切换。

为了解决这个问题,Greenberg 使用了 flutter_platform_widgets 包,尽管他指出布局习惯在不同平台之间仍有显著差异,常常导致需要编写“意大利面式代码”来处理细节。他发现,设计 UI 最有效的方式是参考已经在跨平台平衡上取得成功的应用,尤其是 WhatsApp。

“如果遵循标准的交互习惯,用户会立刻明白它是怎么工作的,因为他们之前已经多次遇到这种交互。”

范围蔓延与说“不”的艺术

当应用在家人之间推广后,各种需求接踵而至:维护计划、里程表扫描的计算机视觉、实时 GPS 追踪等。这迫使开发者对“反特性”进行关键决策。

例如,GPS 追踪被拒绝的原因有两点:电池消耗和隐私。虽然应用需要知道车辆停放的位置,但追踪每一次移动会泄露用户生活的私人细节。通过将这些功能标记为反特性,开发者在不牺牲用户信任或设备性能的前提下,保持了核心实用性。

工程陷阱与迭代

State and Performance(状态与性能)

早期版本出现了卡顿和同步问题。开发者发现,创建“mega-widgets”会导致过度重绘,因为父组件的微小状态变化会触发所有子组件的重新绘制。解决方案是严格重构为更小、更易管理、状态隔离的组件。

The State-Machine Approach(状态机方法)

最初,燃油日志和行程日志是分开的,这让用户感到困惑。Greenberg 将其合并为单一统一的时间线,充当状态机。逻辑变得二元化:车辆只有在归还后才能被取走,归还后才能再次被取走。

回顾这段经历,他指出,如果采用更“抽象”的状态机——即逻辑独立于数据存储方式——将更有利于单元测试,减少 bug 的产生。

社区视角:过度工程 vs. 实用性

该项目在 Hacker News 上引发了争论,是否真的需要这样一个应用。一些批评者认为,一支铅笔和记事本或一个简单的 PWA(渐进式网页应用)就足够了,称该项目是“过度工程”的典型。

然而,也有声音指出,专用应用的价值不仅在于数据记录,更在于它强制的工作流。正如一位评论者所说:

“一个大家真的会打开的粗糙 UI 能胜过由某个人维护的整洁纸质日志……一个专门构建的助手可以成为工作流本身,而不仅仅是记录它。”

其他技术建议包括集成 OBD-II Bluetooth dongles,以自动读取燃油和里程数据,彻底消除手动录入的摩擦。

结论:小众应用的生命周期

最终,OurCar 仍停留在“永远的 alpha”。随着家人搬离,应用的即时需求消失,进一步开发的热情也随之枯竭。尽管如此,这个项目仍然是一次完整的软件开发生命周期练习:从痛点识别、原型制作,到应对应用商店公证以及迭代式 UX 设计的全过程。

Sources