以前我们团队严格执行标准的 GitFlow 分支管理规范:每次迭代都要拉出 feature/*release/*hotfix/* 分支,还要维护万年不变的 devtestprod 三大长期分支。结果每天早上开会,大家光是讨论“这个需求合并到哪个分支了”、“为什么测试环境的代码跟生产对不上”、“谁又把冲突代码强推上去了”就能扯皮一个小时。最痛苦的是每周五的发布日,合并分支引起的冲突多到爆炸,简直是研发效能的终极杀手。

GitFlow 最大的缺陷在于它把“代码集成”和“业务发布”强行绑定在了一起。因为大家觉得只有把代码合入 main 或者 release 分支,才代表这个功能上线了。这就导致有些长周期的开发分支迟迟不能合回主干,拖得越久冲突越严重,最后合并的时候只能求神拜佛。

为了彻底摆脱这种折腾,我们团队在上半年痛下决心,全面转向了主干开发(Trunk-Based Development)。所有人每天都直接提交代码到 main 分支(或通过生命周期极短的 PR 快速合入),取消所有的 devtest 分支。而对于还没开发完或者不想立刻对用户公开的功能,我们统一通过代码里的 Feature Flag(功能开关) 来进行发布拦截。

下面是我们团队目前在 TypeScript 项目中落地的一套超轻量、支持动态评估的 Feature Flag 系统实现:

import { createHash } from 'crypto';

export interface UserContext {
  userId: string;
  email: string;
  role: 'admin' | 'editor' | 'viewer' | 'guest';
  group?: string;
}

export interface FeatureRule {
  enabled: boolean;
  allowedRoles?: Array<UserContext['role']>;
  percentage?: number; // 灰度发布比例:0 - 100
  whiteList?: string[]; // 用户白名单 (userIds)
}

export class FeatureFlagManager {
  private flags: Map<string, FeatureRule> = new Map();

  constructor(initialFlags: Record<string, FeatureRule>) {
    for (const [key, rule] of Object.entries(initialFlags)) {
      this.flags.set(key, rule);
    }
  }

  // 核心评估算法:判断某个用户是否对该功能可见
  public isEnabled(flagName: string, context: UserContext): boolean {
    const rule = this.flags.get(flagName);
    if (!rule) {
      return false; // 默认关闭未定义的安全配置
    }

    if (!rule.enabled) {
      return false;
    }

    // 1. 白名单检查
    if (rule.whiteList && rule.whiteList.includes(context.userId)) {
      return true;
    }

    // 2. 角色限制检查
    if (rule.allowedRoles && !rule.allowedRoles.includes(context.role)) {
      return false;
    }

    // 3. 灰度比例检查 (基于哈希算法分流,保证同一用户的评估结果具有幂等性)
    if (typeof rule.percentage === 'number' && rule.percentage > 0) {
      const hashInput = `${flagName}-${context.userId}`;
      const hashVal = createHash('md5').update(hashInput).digest('hex');
      // 取 md5 的前 8 位转为整数,并对 100 取模
      const userScore = parseInt(hashVal.substring(0, 8), 16) % 100;
      return userScore < rule.percentage;
    }

    return true;
  }
}

// 模拟配置定义(生产环境通常从 Redis 或 Consul 动态拉取)
const localFeatureFlags: Record<string, FeatureRule> = {
  'new-payment-gateway': {
    enabled: true,
    allowedRoles: ['admin', 'editor'],
    whiteList: ['user-10023', 'user-10024'],
    percentage: 10 // 只有 10% 的用户能访问到新网关
  },
  'beta-dashboard-v2': {
    enabled: true,
    percentage: 50 // 对 50% 用户开放
  }
};

// 演示具体使用场景
const manager = new FeatureFlagManager(localFeatureFlags);

const testUser: UserContext = {
  userId: 'user-99999',
  email: 'test@company.com',
  role: 'viewer'
};

if (manager.isEnabled('new-payment-gateway', testUser)) {
  console.log('🚀 执行新版支付网关逻辑');
} else {
  console.log('📦 执行老版支付网关逻辑 (Fallback)');
}

通过这套逻辑,我们把还未测试通过的“新支付网关”代码直接发布到了生产环境,但由于开关限制,只有白名单用户或部分灰度用户能走到对应逻辑。这就达到了“随时合并、随时发布、按需上线”的效果。

引入主干开发之后,我们团队分支冲突的概率下降了 90% 以上。以前发布大版本需要全员戒备、封网三天,现在随时随地都能将最新的 main 分支构建并推送到生产环境,因为未完成的功能已经被 Feature Flag 完美屏蔽掉了。

不过主干开发对单测覆盖率和 CI 自动化门禁的要求非常高。如果大家没有信心建立完善的自动化流水线,主干开发可能会成为线上 Bug 的放大器。

你们团队目前在用哪种分支管理策略?有没有尝试过 Feature Flag 的落地,在落地过程中又遇到了哪些阻碍和踩坑点?