Skip to content

Trunk Based Development 工作流

概述

Trunk Based Development(主干开发)是一种软件开发实践,开发者在一个共享的分支(通常是 maintrunk)上进行协作,通过频繁的小批量合并来保持代码的持续集成。

这种模式被 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-feature

3. 频繁集成

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' },
];

适用场景

适合使用主干开发的场景

  1. 持续部署项目

    • 每天多次部署到生产环境
    • 自动化测试覆盖率高
    • 快速迭代需求
  2. 大型团队

    • Google、Facebook 等数千名开发者
    • 需要高效的协作模式
    • 避免分支合并地狱
  3. 微服务架构

    • 服务独立部署
    • 快速发布周期
    • 小团队自治
  4. SaaS 产品

    • Web 应用
    • 可以快速回滚
    • 用户反馈循环快

不适合使用主干开发的场景

  1. 低测试覆盖率

    • 缺乏自动化测试
    • 手动测试为主
    • 质量风险高
  2. 严格发布周期

    • 固定的发布时间窗口
    • 需要详细的发布计划
    • 监管要求严格
  3. 嵌入式系统

    • 硬件相关开发
    • 发布成本高
    • 难以回滚
  4. 开源项目

    • 贡献者来自外部
    • 需要严格的代码审查
    • 信任度较低

实施要点

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@v3

2. 快速构建和测试

bash
# 优化构建速度
# 使用增量构建
# 并行测试

# 示例: Jest 并行测试
jest --maxWorkers=4

# 示例: 增量构建
npm run build --incremental

3. 自动化部署

yaml
# 自动部署到生产环境
deploy:
  stage: deploy
  script:
    - ./deploy.sh
  only:
    - main
  environment:
    name: production

4. 快速回滚机制

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 基础设施

成功要素:

  • 完善的自动化测试
  • 快速的构建和部署
  • 特性开关技术
  • 快速回滚能力
  • 团队协作文化

选择主干开发的关键因素:

  • 团队的测试成熟度
  • 部署频率要求
  • 团队规模和分布
  • 项目类型和风险容忍度

对于追求快速迭代和持续部署的团队,主干开发是一个值得采用的最佳实践。