为什么功能切换可能是前端最可怕的噩梦

功能切换(或功能标志)已成为前端开发的基本组成部分。它们提供了一种无需部署新代码即可动态启用或禁用功能的方法,从而可以运行 A/B 测试、逐步推出功能或轻松关闭有问题的功能。然而,功能切换在带来诸多好处的同时,也可能带来严峻的挑战。

功能切换的隐藏复杂性

当您开始在前端项目中使用功能切换时,一切似乎都很简单:添加一个切换,检查其值,并确定是否应运行某个功能。但随着项目规模的扩大和您添加更多切换,复杂性会成倍增加。以下是一些常见问题:

  • 配置过载:小型项目可能只有少数几个切换开关,但大型应用程序可能有数十个甚至数百个。管理哪些切换开关处于开启、关闭或处于某种中间状态变得很麻烦。开发人员可能不得不针对不同的环境处理多种配置,这会使调试更加困难。
  • 功能之间的相互依赖性:功能之间的交互方式会带来更隐蔽的问题。原本应该独立切换的功能可能会存在隐藏的依赖关系。如果一个切换改变了共享组件的行为,则依赖于该组件的另一个功能可能会以意想不到的方式中断。代码库的推理变得更加困难,简单的更改可能会带来意想不到的后果。
  • 代码库碎片化:随着您添加功能切换,您的代码开始根据切换状态分成不同的执行路径。随着时间的推移,这会使代码库难以导航。您可能将条件检查分散到各处,代码的可读性和可维护性会受到影响。重构成为一项危险的任务,因为确保正确处理所有切换状态非常棘手且容易出错。
  • 测试挑战:要自信地发布新功能,您必须在所有可能的切换配置中测试应用程序。随着切换数量的增加,组合变得难以管理。即使您自动化测试,覆盖每种情况的成本也会变得非常高昂,尤其是在功能打开和关闭时。
  • 性能开销:功能切换也会带来性能问题。检查切换状态本身并不昂贵,但如果在渲染过程中反复检查多个切换,则可能会降低性能。此外,切换通常需要基础设施进行配置,这会增加后端的负载。
  • 我们如何管理功能切换的复杂性?

    妥善处理功能切换需要周到的策略,有时还需要专门的工具。一些方法包括:

  • 集中配置:不要在整个代码库中散布切换逻辑,而是尝试将其集中起来。使用专用服务或模块来管理所有切换的状态并在一个地方处理复杂的依赖关系。
  • 严格的依赖管理:仔细跟踪哪些功能依赖于其他功能。记录这些关系,如果可能的话,在代码中强制约束,以确保以合理的方式切换功能。
  • 自动化测试和分析:投资于可以分析切换覆盖率的自动化工具。某些系统可以在更改可能破坏现有切换配置时发出警告,或为常见场景生成测试用例。
  • 定期清理:功能切换按钮不应永远存在。一旦功能稳定并向所有用户推出,请移除该切换按钮及其相关逻辑以简化代码库。定期清理流程有助于防止“切换按钮臃肿”。
  • 介绍 app-compose:管理依赖项的新方法

    当在具有许多相互依赖部分的应用程序中使用功能切换时,管理这些依赖关系可能会让人感到不知所措。这时,app-compose 之类的工具可以提供帮助。

    app-compose 允许您将功能定义为独立的容器,每个容器都具有显式依赖关系。您无需手动跟踪哪些功能已准备就绪或处理复杂的条件链,app-compose 会为您协调一切。通过使用 app-compose,您可以确保功能仅在其依赖关系处于正确状态时初始化,从而显著降低隐藏错误或运行时错误的风险。

    这是一个关于 app-compose 如何简化依赖管理的简单示例:

    import { createContainer, compose } from '@grlt-hub/app-compose'
    
    const fetchToggles = () => ({ referral: false });
    
    const featureToggle = createContainer({
      id: 'featureToggle',
      start: async () => {
        const data = await fetchToggles();
    
        return { api: { data } };
      },
    });
    
    const referral = createContainer({
      id: 'referral',
      dependsOn: [featureToggle],
      enable: (deps) => deps.featureToggle.data.referral,
      start: () => ({ api: null }),
    });
    
    await compose.up([featureToggle, referral]);

    解释:

  • fetchToggles:获取功能切换并返回一个对象,指示每个功能是启用还是禁用。
  • featureToggle:获取切换的容器。数据通过其 API 提供。
  • referral:依赖于 featureToggle 的容器。它使用 enable 方法检查引荐功能是否已启用。如果未启用该功能,则引荐容器不会启动。
  • 使用 app-compose,控制功能切换的复杂性不仅变得易于管理,而且变得结构化和高效。

    文档

    GitHub 仓库