以前我们团队严格执行标准的 GitFlow 分支管理规范:每次迭代都要拉出 feature/*、release/*、hotfix/* 分支,还要维护万年不变的 dev、test、prod 三大长期分支。结果每天早上开会,大家光是讨论“这个需求合并到哪个分支了”、“为什么测试环境的代码跟生产对不上”、“谁又把冲突代码强推上去了”就能扯皮一个小时。最痛苦的是每周五的发布日,合并分支引起的冲突多到爆炸,简直是研发效能的终极杀手。
GitFlow 最大的缺陷在于它把“代码集成”和“业务发布”强行绑定在了一起。因为大家觉得只有把代码合入 main 或者 release 分支,才代表这个功能上线了。这就导致有些长周期的开发分支迟迟不能合回主干,拖得越久冲突越严重,最后合并的时候只能求神拜佛。
为了彻底摆脱这种折腾,我们团队在上半年痛下决心,全面转向了主干开发(Trunk-Based Development)。所有人每天都直接提交代码到 main 分支(或通过生命周期极短的 PR 快速合入),取消所有的 dev 和 test 分支。而对于还没开发完或者不想立刻对用户公开的功能,我们统一通过代码里的 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 的落地,在落地过程中又遇到了哪些阻碍和踩坑点?