Actual 实验性功能(Feature Flags)管理机制深度解析:从政策文档到源码实现
【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual
Actual 是一款本地优先(local-first)的个人财务管理应用。为了保证代码库不被半成品功能污染,Actual 团队于 2024 年 2 月正式引入了针对实验性功能(Experimental Features / Feature Flags)的专项管理政策。本文以官方公告博客与配套的 feature-flags 文档 为主线,结合桌面端设置面板与偏好系统源码,完整解读这套政策的具体规则、运行机制与源码级实现,帮助用户与贡献者正确理解实验性功能的使用边界与生命周期管理。
政策出台的背景
随着 Actual 功能版图的扩张,实验性功能(即由特性开关控制的未完成功能)数量开始增加。若放任不管,这些半成品会长期滞留代码库,造成两个直接后果:
- 代码库被未完成的功能持续污染,可读性与可维护性下降;
- 未经验证的功能长期暴露给真实用户,可能造成数据损坏等风险。
为此,官方在博客 2024-02-20-experimental-features.md 中宣布了新政策,核心诉求是:确保代码库不会堆满未完成的功能。政策全文(含 FAQ)收录于 feature-flags.md,并已挂载进文档站侧边栏(见 docs-sidebar.js)。
政策核心:两条硬性规则
官方公告给出的"短版本"是两条规则,这也是整套政策的骨架:
- 废弃的实验性功能将被从代码库中移除——如果一个实验性功能长期没有活跃开发,它就不再被容忍滞留;
- 实验性功能不能用作小型视觉/功能怪癖的开关——例如"类别选择器是否显示隐藏类别"这类细枝末节,不允许做成一个开关让用户切换。
第二条规则直接对应 Actual 的产品哲学:简洁、无杂物(sleek and clutter-free)。配置页本身也不该堆满为每个 UI 小怪癖服务的选项。文档明确说明:如果你确实需要此类自定义,请 fork UI 仓库自行实现,官方只支持一种用例、不提供二选一开关。
Feature Flags 是什么:定义与典型用法
定义
Feature flags(实验性功能)是应用中用来启用或禁用某些功能的开关机制。它在两种典型场景下发挥作用:
- 小范围灰度测试:在功能全量推送给所有人之前,先让一小部分真实用户试用;
- 大型功能分块交付:将一个复杂大功能拆分成多个可独立发布的小块,逐块上线。
官方实例:Custom Reports 的演进路径
文档给出了一个极具参考价值的真实案例——自定义报表(Custom Reports):
最初以只读版本在 feature flag 之下发布,之后才逐步加入保存功能。
这种"先放只读骨架、后补交互能力"的节奏,让功能在宣布为稳定的一等公民(first-party)之前,就能先交到真实用户手中收集反馈,而不是关在实验室里自嗨。
收益与代价:政策为何如此严格
Feature Flags 的两大收益
- 把大而复杂的功能拆成更小的交付物,降低单次发布的体积与风险;
- 尽早发布给真实用户收集反馈,让开发方向由实际使用数据驱动。
Feature Flags 的代价
- 让代码更复杂、更难理解——每个开关都意味着一条额外分支;
- 管理不当会积累技术债务——废弃开关长期残留,未来无人敢删。
正是因为收益与代价并存,官方才制定了严格的管理政策来约束其生命周期。
3 个月清理政策:实验性功能的生命周期
规则原文
超过 3 个月没有任何活跃开发的实验性功能将从代码库中移除。
这一条是政策的核心执行条款,目的是防止代码库被未完成功能堆积。文档同时给出了两个配套约定:
- 移除前会尽力沟通:在移除某个实验性功能开关之前,维护团队会尽力联系其原始实现工程师;若联系不上,则直接移除。
- 欢迎"复活":如果你愿意承诺把它做完并发布为一等公民功能,完全可以把它重新带回来——但前提是你能承诺协助完成它直至正式发布。
为什么这么严格
文档坦承了背后的资源现实:核心维护团队没有精力同时维护大量实验性功能,也没有能力去完成被原作者遗弃的功能。但对于正在积极开发中的功能,团队明确表示乐意提供支持。这是一套"要么把它做完、要么被清理"的清晰权责划分。
FAQ 详解:两个高频问题
能否把 Feature Flag 当作配置项使用?
不能。前文已述,Actual 的设计哲学是"简洁、无杂物",这同时约束着配置页和 feature flags。文档明确不希望在 UI 上为每个小怪癖提供一个配置选项,并再次以"类别选择器是否包含隐藏类别"为例说明:官方只支持一种用例,不会提供切换开关。
为什么我的 Feature Flag 被移除了?
- 短答案:很可能该功能已超过 3 个月没有活跃开发。只要你承诺继续开发直至作为一等公民功能发布,就可以把功能带回来。
- 长答案:即上文全部生命周期政策。
源码级实现:设置面板中的实验性功能开关
政策落到实际产品中,是桌面端「设置 → 实验性功能」面板。其完整实现位于 Experimental.tsx,运行流程清晰可循。
第一步:风险确认墙
面板默认处于收起状态,只展示一个风险提示文案与「I understand the risks, show experimental features」链接(对应源码中的expanded状态,见 Experimental.tsx 与 Experimental.tsx)。用户必须主动点击确认才能展开开关列表。这堵"确认墙"是产品层面对实验性功能风险的第一道防线。
第二步:醒目的数据安全警告
展开后的功能描述文案极其直白:
Experimental features.These features are not fully tested and may not work as expected. THEY MAY CAUSE IRRECOVERABLE DATA LOSS. They may do nothing at all. Only enable them if you know what you are doing.
(这些功能未经充分测试,可能无法按预期工作。它们可能造成不可恢复的数据丢失,也可能什么都不会发生。只有在你明确自己在做什么时才启用。)
这段警告来自 Experimental.tsx,由 i18n 的<Trans>组件包装,可随语言包翻译。
第三步:FeatureToggle 开关组件
面板中每个功能开关都由FeatureToggle组件渲染(见 Experimental.tsx),其核心逻辑为:
- 通过
useFeatureFlag(flagName)读取当前是否启用; - 通过
useSyncedPref('flags.${flagName}')写入开关值; - 点击
Checkbox时将当前布尔值取反后写回(setFlagPref(String(!enabled))); - 可选地展示
feedbackLink(反馈链接)、error(禁用原因)与note(如"已废弃"提示)。
一个值得注意的细节是:开关值以字符串形式写入同步偏好('true'/'false'),这与偏好系统的存储模型保持一致。
偏好系统的底层机制:开关如何被持久化与读取
读写链路
FeatureToggle所依赖的两个 Hook 位于 useFeatureFlag.ts 与 useSyncedPref.ts:
useSyncedPref通过 redux 的saveSyncedPrefsaction 将{ [prefName]: value }分发写入全局状态(见 useSyncedPref.ts);useFeatureFlag将开关名包装为flags.${name}键后委托给useSyncedPref读取(见 useFeatureFlag.ts)。
默认状态:全部关闭
useFeatureFlag中维护了一张默认状态表(见 useFeatureFlag.ts):当偏好中尚无对应值时,所有 14 个 feature flag 的默认值均为false。读取逻辑为:偏好值为undefined时取默认值,否则以字符串严格等于'true'判定启用。这意味着任何实验性功能默认都是关闭的,必须由用户在设置面板中主动开启。
类型层面的约束
Feature flag 的名称不是随意字符串,而是由 TypeScript 联合类型强约束的。在 prefs.ts 中,FeatureFlag类型枚举了全部合法开关名,而SyncedPrefs通过模板字面量类型flags.${FeatureFlag}(见 prefs.ts)将其纳入跨设备同步偏好体系——这保证了编译期就能发现拼写错误的开关名。
当前实验性功能清单与典型应用
截至当前仓库版本,FeatureFlag联合类型共定义了 14 个实验性开关(见 prefs.ts),它们在设置面板中呈现为:
| 开关名 | 功能说明 |
|---|---|
newSidebarUI | 新侧边栏 UI |
goalTemplatesEnabled | 目标模板(Goal templates) |
goalTemplatesUIEnabled | 子功能:预算自动化 UI(仅在上一项开启时展示) |
actionTemplating | 规则动作模板化(已标记废弃,提示改用 Excel 公式模式 / Rule formulae,见 Experimental.tsx) |
formulaMode | Excel 公式模式(公式卡片与规则公式) |
currency | 货币支持 |
balanceForecastReport | 余额预测报表 |
customThemes | 自定义主题 |
budgetAnalysisReport | 预算分析报表 |
enableBanking | Enable Banking 同步(欧盟银行) |
sankeyReport | 桑基图报表 |
akahuBankSync | Akahu 银行同步(新西兰银行) |
mobileCalculator | 移动端计算器 |
monteCarloReport | 蒙特卡洛分析报表 |
典型源码用例:flags.currency的实际读取
以货币功能为例,在 customFunctionsPreferences.ts 中,服务端通过读取偏好flags.currency === 'true'来判断是否启用货币相关自定义函数(见 customFunctionsPreferences.ts)。这展示了 feature flag 在服务端计算路径中的典型用法:同一开关同时驱动 UI 展示与后端逻辑。
面向多用户的服务器偏好:flags.plugins
除客户端同步偏好外,还存在服务端级别的实验性开关。ServerPrefs中定义了'flags.plugins': 'true' | 'false'(见 prefs.ts)。设置面板通过ServerFeatureToggle组件(见 Experimental.tsx)渲染,该组件带有更严格的展示条件:
- 仅当存在同步服务器且服务器在线时显示;
- 多用户模式下仅对管理员可见(OIDC 登录时),或单用户模式对所有人可见;
- 该开关当前被标记为
disableToggle(禁用切换),文案为"Client-Side plugins (soon)",且需要开发者在浏览器 localStorage 中设置devEnableServerPrefs === 'true'才会显示(见 Experimental.tsx)。
这组条件体现了实验性功能在多用户/服务端场景下的额外权限与状态管控。
给贡献者的实践指南
结合政策文档与源码,贡献者在引入或维护实验性功能时应遵循以下要点:
- 命名即契约:将开关名加入
FeatureFlag联合类型(prefs.ts),获得类型安全与同步偏好支持。 - 默认关闭:在 useFeatureFlag.ts 的默认状态表中登记新开关并保持
false。 - UI 接入:在 Experimental.tsx 中新增一个
FeatureToggle,按需附带feedbackLink、note等辅助信息;废弃的功能请像actionTemplating一样标注废弃提示。 - 承诺完成:如果长期不活跃,功能将面临 3 个月后被移除的命运;被移除后想复活,需承诺协助完成至一等公民发布。
- 勿滥用为配置项:小型视觉/功能怪癖不允许使用 feature flag 开关,请 fork 自行实现。
总结
Actual 的实验性功能政策是一套"鼓励尝试、约束滞留"的平衡机制:Feature Flags 让大型功能可以分块交付、灰度验证,但 3 个月无活跃开发即清理的硬性规则,配合同步偏好与类型系统的工程支撑,确保代码库始终整洁、可控。理解这套机制,无论是作为用户谨慎地开启实验性功能,还是作为贡献者规划功能的引入与收尾,都能在 Actual 的生态中少走弯路——开启开关前,请务必记住设置面板中那句警告:它们可能造成不可恢复的数据丢失。
【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考