特性切换:软件开发中的高效功能管理策略

155 阅读6分钟

引言

特性切换是现代软件开发中的常见模式,通过灵活的功能管理,使得团队能够更高效地进行开发、测试和发布。特性切换 Feature Toggles/ Feature Flag 作为一种高效的功能管理技术,能够在应用运行时动态启用或禁用功能,而无需更改代码。简单来说,特性切换就像是一个“开关”,开发者可以根据需要灵活控制某些功能的开启与关闭,极大地提升了开发和部署的效率。

特性切换的基本概念可以被理解为一个简单的 if/else 语句,可以在运行时根据条件判断来控制程序行为。通过配置文件或代码中的开关,可以在运行时启用或禁用某个功能。这种做法使得开发者能够在不需要重新发布应用的情况下,实时调整软件的功能。除了简单的开/关控制外,特性切换还可以根据不同的用户、环境或时间动态变化,满足多种需求,如渐进式发布、A/B 测试等,还能让团队在功能开发过程中灵活控制新特性的发布,从而避免频繁的发布和回滚。

1. 特点:

  • 在运行时动态启用或禁用功能,而无需更改代码。
  • 可以基于条件(如用户角色、地理位置等)控制某些功能的开启。
  • 特性切换通过灵活的配置管理,使得开发团队能够轻松控制功能的发布与回滚,提升迭代速度。

2. 特性切换的优势

  • 灵活性:特性切换使得开发团队能够在应用运行时启用或禁用某些功能,而无需发布新版本。这大大提高了灵活性,可以即时响应用户反馈和市场需求。
  • 降低风险:在发布新功能时,特性切换允许开发者逐步启用新功能或进行渐进式发布,降低了大规模发布时出现故障的风险。
  • 加速开发周期:特性切换帮助开发团队避免长时间的功能分支和合并过程,可以快速迭代并进行小范围的功能测试和验证。
  • 提高用户体验:根据不同用户群体或特定条件,特性切换能够精准控制某些功能的发布,提供个性化体验。

3. 常见的特性切换类型

特性切换有多种类型,根据不同的应用场景可以选择不同的方式进行实现:

  1. 开/关开关(On/Off Toggles):用于简单的功能启用/禁用。在团队协作时,可以在同一代码库中开发不同的功能,而不必担心某些功能的未完成部分影响整体发布。
  2. 权限开关(Permission Toggles):基于用户角色、权限等特定条件启用或禁用功能。例如,根据用户的订阅级别或账户状态来决定是否开启某个功能。
  3. 渐进发布(Canary Toggles):在多阶段发布中使用,通过逐步将功能推出给用户,先在小范围内进行验证,逐渐扩大到所有用户。
  4. 实验性切换(Experiment Toggles):用于 A/B 测试等实验功能,通过特性切换实时调整功能,评估不同版本的表现。A/B 测试常常基于用户群进行统计学分组,其实现使用到hash算法进行分桶,针对不同桶设置不同的参数。
  5. 环境切换(Environment Toggles):根据不同的环境(如开发、测试、生产环境)启用不同的功能,确保各环境之间的功能差异。

Martin Fowler 根据有效时长和动态性对于特性切换做了分类,如下图:

image.png

4. 举例

Togglz 是一个流行的特性切换类库,提供了一些高级功能,例如:

  • Username:根据用户名启用特性,支持特定用户的功能切换。
  • Gradual Rollout:通过逐步启用功能,进行 Canary 发布,评估新功能的表现。
  • Release Date:可以在指定日期启用特性,适用于定时发布、促销活动等场景。
  • Client IP:基于客户端 IP 启用功能,可以根据地理位置或其他因素控制功能发布。

Togglz 提供了与 Spring Boot 的集成,可以快速在项目中启用特性切换,支持多种激活策略和灵活的配置管理。

// 特性为枚举类型
public enum MyFeatures implements Feature {

    @Label("First Feature")
    FEATURE_ONE,

    @Label("Second Feature")
    FEATURE_TWO;

    public boolean isActive() {
        return FeatureContext.getFeatureManager().isActive(this);
    }

}

public void someBusinessMethod() {

  // 通过第三方(依赖)实现特性管理,避免耦合,方便测试
  // 使用时同使用 boolean 一样
  if( MyFeatures.FEATURE_ONE.isActive() ) {
    // do new exciting stuff here
  }

  [...]

}

Togglez 使用策略模式表示求取开关的过程,包括动态求值(动态表达式求值)和静态求值,不妨看下其基于用户角色的策略实现:

/**
 * ActivationStrategy implementation based on roles of the current user. As far as user has at least one of configured roles
 * then feature will be active.
 * <p/>
 * Please note this activation strategy is not coupled to any particular security framework and will work with any framework as
 * far as current user has "roles" attribute populated with a set of granted authorities. This is usually a responsibility of an
 * UserProvider.
 * 
 * @author Vasily Ivanov
 */
public class UserRoleActivationStrategy implements ActivationStrategy {
    // 略去其他
    @Override
    public boolean isActive(FeatureState state, FeatureUser user) {
        if (user != null) {
            Collection<String> userRoles = 
                (Collection<String>) user.getAttribute(USER_ATTRIBUTE_ROLES);
            if (userRoles != null) {
                String rolesAsString = state.getParameter(PARAM_ROLES_NAME);
                if (Strings.isNotBlank(rolesAsString)) {
                    List<String> roles = Strings.splitAndTrim(rolesAsString, ",");
                    for (String authority : roles) {
                        if (userRoles.contains(authority)) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    @Override
    public Parameter[] getParameters() {
        return new Parameter[] {
                ParameterBuilder.create(PARAM_ROLES_NAME)
                    .label(PARAM_ROLES_LABEL)
                    .description(PARAM_ROLES_DESC)
                    .largeText()
        };
    }
}

实现和一些权限管理框架类似,开发者需要自行决定这种实现的单一职责归谁所有。

OpenFeature 是一个开源的特性开关规范,其做了很多有益的抽象,提出了服务提供方、求值API、钩子、事件、追踪等定义:

image.png 提供的使用例子如下:

package com.demo;

import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.OpenFeatureAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RestHello {
    private final OpenFeatureAPI openFeatureAPI;

    @Autowired
    public RestHello(OpenFeatureAPI OFApi) {
        this.openFeatureAPI = OFApi;
    }

    @GetMapping("/hello")
    public String getHello() {
        final Client client = openFeatureAPI.getClient();

        // Evaluate welcome-message feature flag
        if (client.getBooleanValue("welcome-message", false)) {
            return "Hello, welcome to this OpenFeature-enabled website!";
        }

        return "Hello!";
    }
}

实现基本思想和 Togglez 一致,即引入中间层实现解耦,最终使用布尔值表示特性是否打开。

5. 最佳实践

为了有效使用特性切换,开发团队应遵循一些最佳实践:

  • 统一命名与管理:统一特性切换的命名规则和管理策略,避免混乱。不同项目应遵循相同的特性切换实现范式。
  • 清理过期的切换:定期清理不再需要的特性切换,减少技术债务。
  • 确保充分测试:在所有可能的特性切换状态下都进行充分的测试,避免因状态未覆盖的部分导致的问题。
  • 监控与分析:运行时监控特性切换的效果,确保不会引入不必要的负担。逐步启用新功能并监控其影响,避免一次性大规模推出。
  • 注意复杂性和技术债:开发者需要小心管理特性切换的数量和复杂度,确保其不会带来过多的技术债务。