Appearance
Trunk Based Development 工作流
概述
Trunk Based Development(主干开发)是一种软件开发实践,开发者在一个共享的分支(通常是 main 或 trunk)上进行协作,通过频繁的小批量合并来保持代码的持续集成。
这种模式被 Google、Facebook、Microsoft 等大型科技公司广泛采用,是持续集成和持续部署(CI/CD)的最佳实践之一。
核心理念:
- 只有一个长期分支:
main(或trunk) - 所有开发都在主干上进行
- 频繁提交,小批量变更
- 使用特性开关控制功能发布
主干开发
基本流程
main ──●──●──●──●──●──●──●──●──●──●──
\ / \ / \ /
short ●──● ●──● ●──●
lived
branches核心原则
1. 单一主干分支
bash
# 主干分支命名
main
# 或
trunk
# 所有代码最终都合并到主干
# 没有长期的开发分支2. 短生命周期分支
bash
# 分支生命周期应该很短(通常少于一天)
# 最多不超过 3 天
# 创建短期分支
git checkout main
git pull origin main
git checkout -b short-feature
# 快速开发和测试
git add .
git commit -m "Add feature"
# 合并回主干
git checkout main
git merge short-feature
git push origin main
# 立即删除分支
git branch -d short-feature3. 频繁集成
bash
# 每天至少提交一次到主干
# 理想情况: 每小时多次提交
# 小步提交
git commit -m "Add user model"
git push origin main
git commit -m "Add user validation"
git push origin main
git commit -m "Add user tests"
git push origin main工作流程
标准开发流程
bash
# 1. 更新主干
git checkout main
git pull origin main
# 2. 创建短期分支(可选)
git checkout -b add-login
# 3. 快速开发
# 编写代码
git add .
git commit -m "Add login functionality"
# 4. 运行测试
npm test
# 5. 合并到主干
git checkout main
git merge add-login
# 或使用 rebase 保持线性历史
git checkout main
git rebase add-login
# 6. 推送到远程
git push origin main
# 7. 删除短期分支
git branch -d add-login直接在主干上开发
bash
# 对于小型变更,可以直接在主干上开发
git checkout main
git pull origin main
# 进行修改
git add .
git commit -m "Fix typo in README"
git push origin main代码审查
Pre-commit 审查
bash
# 使用 Gerrit 等工具进行提交前审查
git push origin HEAD:refs/for/main
# 或使用 GitHub/GitLab 的 PR/MR
# 但保持 PR 生命周期很短(几小时内)Post-commit 审查
bash
# 提交后进行代码审查
# 如果发现问题,立即修复
git commit --amend
# 或
git revert <commit-hash>特性开关
特性开关(Feature Flags)是主干开发的关键技术,允许未完成的代码存在于主干中而不影响生产环境。
特性开关类型
1. 发布开关(Release Toggles)
javascript
// 用于隐藏未完成的功能
const features = {
newDashboard: process.env.ENABLE_NEW_DASHBOARD === 'true',
advancedSearch: false,
};
function renderDashboard() {
if (features.newDashboard) {
return <NewDashboard />;
}
return <OldDashboard />;
}2. 实验开关(Experiment Toggles)
javascript
// 用于 A/B 测试
const experiments = {
newCheckout: {
enabled: true,
percentage: 10, // 10% 的用户看到新版本
},
};
function getCheckoutFlow(userId) {
if (experiments.newCheckout.enabled &&
isUserInExperiment(userId, experiments.newCheckout.percentage)) {
return 'new-checkout';
}
return 'old-checkout';
}3. 运维开关(Ops Toggles)
javascript
// 用于系统运维控制
const opsFlags = {
maintenanceMode: false,
disablePayments: false,
throttleApi: true,
};
function handleRequest(req, res) {
if (opsFlags.maintenanceMode) {
return res.status(503).send('Maintenance');
}
// 正常处理
}特性开关实现
配置文件方式
yaml
# config/features.yml
features:
new_dashboard:
enabled: true
rollout: 50
advanced_search:
enabled: false
beta_features:
enabled: true
users: ['user1', 'user2', 'user3']数据库方式
sql
CREATE TABLE feature_flags (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
enabled BOOLEAN DEFAULT false,
config JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO feature_flags (name, enabled, config)
VALUES ('new_dashboard', true, '{"rollout": 50}');特性开关服务
javascript
class FeatureFlagService {
constructor() {
this.flags = new Map();
}
async loadFlags() {
const flags = await db.query('SELECT * FROM feature_flags');
flags.forEach(flag => {
this.flags.set(flag.name, {
enabled: flag.enabled,
config: flag.config,
});
});
}
isEnabled(flagName, userId = null) {
const flag = this.flags.get(flagName);
if (!flag) return false;
if (!flag.enabled) return false;
// 检查用户白名单
if (flag.config.users && userId) {
return flag.config.users.includes(userId);
}
// 检查灰度比例
if (flag.config.rollout && userId) {
return this.isUserInRollout(userId, flag.config.rollout);
}
return true;
}
isUserInRollout(userId, percentage) {
const hash = this.hashUserId(userId);
return (hash % 100) < percentage;
}
hashUserId(userId) {
// 简单的哈希函数
let hash = 0;
for (let i = 0; i < userId.length; i++) {
hash = ((hash << 5) - hash) + userId.charCodeAt(i);
hash = hash & hash;
}
return Math.abs(hash);
}
}
module.exports = new FeatureFlagService();特性开关最佳实践
1. 及时清理
javascript
// 不要让特性开关永久存在
// 功能稳定后,删除开关代码
// 阶段1: 添加开关
if (featureFlags.isEnabled('new-dashboard')) {
renderNewDashboard();
} else {
renderOldDashboard();
}
// 阶段2: 功能稳定后,移除开关
renderNewDashboard();
// 删除旧代码和开关配置2. 监控和日志
javascript
function renderDashboard() {
if (featureFlags.isEnabled('new-dashboard')) {
logger.info('Using new dashboard', { feature: 'new-dashboard' });
return renderNewDashboard();
}
logger.info('Using old dashboard', { feature: 'new-dashboard' });
return renderOldDashboard();
}3. 灰度发布
javascript
// 逐步增加灰度比例
const rolloutStages = [
{ percentage: 1, duration: '1 day' },
{ percentage: 5, duration: '2 days' },
{ percentage: 25, duration: '3 days' },
{ percentage: 50, duration: '3 days' },
{ percentage: 100, duration: 'forever' },
];适用场景
适合使用主干开发的场景
持续部署项目
- 每天多次部署到生产环境
- 自动化测试覆盖率高
- 快速迭代需求
大型团队
- Google、Facebook 等数千名开发者
- 需要高效的协作模式
- 避免分支合并地狱
微服务架构
- 服务独立部署
- 快速发布周期
- 小团队自治
SaaS 产品
- Web 应用
- 可以快速回滚
- 用户反馈循环快
不适合使用主干开发的场景
低测试覆盖率
- 缺乏自动化测试
- 手动测试为主
- 质量风险高
严格发布周期
- 固定的发布时间窗口
- 需要详细的发布计划
- 监管要求严格
嵌入式系统
- 硬件相关开发
- 发布成本高
- 难以回滚
开源项目
- 贡献者来自外部
- 需要严格的代码审查
- 信任度较低
实施要点
1. 完善的自动化测试
yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: |
npm ci
npm test
npm run e2e
- name: Code coverage
run: npm run coverage
- name: Upload coverage
uses: codecov/codecov-action@v32. 快速构建和测试
bash
# 优化构建速度
# 使用增量构建
# 并行测试
# 示例: Jest 并行测试
jest --maxWorkers=4
# 示例: 增量构建
npm run build --incremental3. 自动化部署
yaml
# 自动部署到生产环境
deploy:
stage: deploy
script:
- ./deploy.sh
only:
- main
environment:
name: production4. 快速回滚机制
bash
# 一键回滚
git revert HEAD
git push origin main
# 或使用部署工具回滚
kubectl rollout undo deployment/myapp最佳实践
提交规范
bash
# 小步提交,每个提交都应该可工作
git commit -m "Add user model"
git commit -m "Add user validation"
git commit -m "Add user tests"
# 而不是一个大提交
git commit -m "Add complete user system with model, validation, and tests"代码审查
markdown
## 快速审查原则
1. 每个提交都应该在几小时内完成审查
2. 审查者应该优先处理待审查的代码
3. 使用自动化工具减少人工审查负担
4. 关注架构和设计,而非代码风格监控和告警
yaml
# 监控关键指标
metrics:
- name: deployment_frequency
target: "> 1 per day"
- name: lead_time
target: "< 1 hour"
- name: change_failure_rate
target: "< 5%"
- name: mean_time_to_recovery
target: "< 1 hour"常见问题
如何处理大型重构?
bash
# 方法1: 分支抽象(Branch by Abstraction)
// 阶段1: 创建抽象层
interface PaymentService {
processPayment(): Promise<void>;
}
class OldPaymentService implements PaymentService {
processPayment() {
// 旧实现
}
}
// 阶段2: 添加新实现
class NewPaymentService implements PaymentService {
processPayment() {
// 新实现
}
}
// 阶段3: 使用特性开关切换
function getPaymentService(): PaymentService {
if (featureFlags.isEnabled('new-payment')) {
return new NewPaymentService();
}
return new OldPaymentService();
}
// 阶段4: 移除旧实现
function getPaymentService(): PaymentService {
return new NewPaymentService();
}如何处理破坏性变更?
bash
# 使用特性开关和兼容性层
// 保持向后兼容
function apiEndpoint(req, res) {
const version = req.headers['api-version'] || 'v1';
if (version === 'v2') {
return handleV2(req, res);
}
return handleV1(req, res);
}如何保证代码质量?
bash
# 1. 自动化测试
npm test
# 2. 代码静态分析
npm run lint
npm run type-check
# 3. 代码审查
# 使用 GitHub/GitLab 的 PR 功能
# 4. 持续监控
# 监控生产环境指标总结
主干开发是一种高效的开发模式,特别适合现代软件开发:
优势:
- 快速反馈: 快速发现和解决问题
- 减少冲突: 频繁集成减少合并冲突
- 持续交付: 支持持续部署
- 简化流程: 没有复杂的分支管理
挑战:
- 测试要求: 需要完善的自动化测试
- 文化转变: 需要团队适应新的工作方式
- 工具支持: 需要强大的 CI/CD 基础设施
成功要素:
- 完善的自动化测试
- 快速的构建和部署
- 特性开关技术
- 快速回滚能力
- 团队协作文化
选择主干开发的关键因素:
- 团队的测试成熟度
- 部署频率要求
- 团队规模和分布
- 项目类型和风险容忍度
对于追求快速迭代和持续部署的团队,主干开发是一个值得采用的最佳实践。
