trigger-refactor-pipeline

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

When to Use This Skill

何时使用此技能

Use this skill when you need to:
  • Modernize legacy triggers with DML/SOQL operations inside loops
  • Refactor triggers that lack clear separation of concerns
  • Implement bulk-safe patterns in existing trigger code
  • Generate comprehensive test coverage for refactored triggers
当你需要以下操作时,可使用此技能:
  • 对循环内包含DML/SOQL操作的遗留触发器进行现代化改造
  • 重构关注点分离不清晰的触发器
  • 在现有触发器代码中实现批量安全模式
  • 为重构后的触发器生成全面的测试覆盖率

Prerequisites

前置条件

Before starting, ensure you have:
  1. Salesforce CLI installed and authenticated to your target org
  2. Python 3.9 or higher installed
  3. The baseline trigger deployed (see Setup section)
开始之前,请确保你已具备:
  1. 已安装Salesforce CLI并完成目标组织的身份验证
  2. 已安装Python 3.9或更高版本
  3. 已部署基线触发器(请参阅设置部分)

Setup

设置步骤

Deploy the baseline anti-pattern trigger to analyze and refactor:
apex
// ❌ Anti-pattern: all logic stuffed into the trigger, with DML/SOQL in loops.
trigger OpportunityTrigger on Opportunity (before insert, before update, after update) {
    // BEFORE INSERT: validate Closed Won w/ low Amount
    if (Trigger.isBefore && Trigger.isInsert) {
        for (Opportunity o : Trigger.new) {
            if (o.StageName == 'Closed Won' && (o.Amount == null || o.Amount < 1000)) {
                o.addError('Closed Won opportunities must have Amount ≥ 1000.');
            }
        }
    }

    // BEFORE UPDATE: if Stage changed, overwrite Description
    if (Trigger.isBefore && Trigger.isUpdate) {
        for (Opportunity o : Trigger.new) {
            Opportunity oldO = Trigger.oldMap.get(o.Id);
            if (o.StageName != oldO.StageName) {
                o.Description = 'Stage changed from ' + oldO.StageName + ' to ' + o.StageName;
            }
        }
    }

    // AFTER UPDATE: when Stage becomes Closed Won, create a follow-up Task
    if (Trigger.isAfter && Trigger.isUpdate) {
        for (Opportunity o : Trigger.new) {
            Opportunity oldO = Trigger.oldMap.get(o.Id);
            if (o.StageName == 'Closed Won' && oldO.StageName != 'Closed Won') {
                Task t = new Task(
                    WhatId     = o.Id,
                    OwnerId    = o.OwnerId,
                    Subject    = 'Send thank-you',
                    Status     = 'Not Started',
                    Priority   = 'Normal',
                    ActivityDate = Date.today()
                );
                insert t; // ❌ DML in a loop
            }
        }
    }
}
Deploy this to your org:
bash
sf project deploy start --source-dir force-app/main/default/triggers
部署用于分析和重构的基线反模式触发器:
apex
// ❌ 反模式:所有逻辑都塞进触发器,循环内包含DML/SOQL操作。
trigger OpportunityTrigger on Opportunity (before insert, before update, after update) {
    // BEFORE INSERT:验证金额较低的Closed Won商机
    if (Trigger.isBefore && Trigger.isInsert) {
        for (Opportunity o : Trigger.new) {
            if (o.StageName == 'Closed Won' && (o.Amount == null || o.Amount < 1000)) {
                o.addError('Closed Won商机的金额必须≥1000。');
            }
        }
    }

    // BEFORE UPDATE:若阶段变更,覆盖描述信息
    if (Trigger.isBefore && Trigger.isUpdate) {
        for (Opportunity o : Trigger.new) {
            Opportunity oldO = Trigger.oldMap.get(o.Id);
            if (o.StageName != oldO.StageName) {
                o.Description = '阶段从' + oldO.StageName + '变更为' + o.StageName;
            }
        }
    }

    // AFTER UPDATE:当阶段变为Closed Won时,创建跟进任务
    if (Trigger.isAfter && Trigger.isUpdate) {
        for (Opportunity o : Trigger.new) {
            Opportunity oldO = Trigger.oldMap.get(o.Id);
            if (o.StageName == 'Closed Won' && oldO.StageName != 'Closed Won') {
                Task t = new Task(
                    WhatId     = o.Id,
                    OwnerId    = o.OwnerId,
                    Subject    = '发送感谢信',
                    Status     = '未开始',
                    Priority   = '正常',
                    ActivityDate = Date.today()
                );
                insert t; // ❌ 循环内执行DML
            }
        }
    }
}
将其部署到你的组织:
bash
sf project deploy start --source-dir force-app/main/default/triggers

Step 1: Analyze the Trigger

步骤1:分析触发器

Run the analysis script to identify anti-patterns and generate a report:
bash
python scripts/analyze_trigger.py OpportunityTrigger
The script will output:
  • DML in loops - Line numbers where DML operations occur inside iteration
  • SOQL in loops - Line numbers where SOQL queries occur inside iteration
  • Missing bulkification - Areas where collection-based processing is needed
  • Complexity score - Overall trigger complexity rating (1-10)
  • Recommended approach - Suggested handler pattern based on trigger contexts
Review the analysis report before proceeding to refactoring.
运行分析脚本识别反模式并生成报告:
bash
python scripts/analyze_trigger.py OpportunityTrigger
脚本将输出:
  • 循环内DML操作 - 循环内执行DML操作的行号
  • 循环内SOQL查询 - 循环内执行SOQL查询的行号
  • 缺失批量优化 - 需要基于集合处理的区域
  • 复杂度评分 - 触发器整体复杂度评级(1-10)
  • 推荐方案 - 基于触发器上下文的建议处理器模式
在进行重构前,请先查看分析报告。

Step 2: Review Handler Patterns

步骤2:查看处理器模式

Consult the handler patterns reference to understand:
  • Single-responsibility handlers - One handler class per trigger context
  • Unified handler approach - Single handler with context methods
  • Bulk collection strategies - How to aggregate DML/SOQL outside loops
  • Best practices - Error handling, test boundaries, deployment order
Choose the pattern that best fits your trigger's complexity and team conventions.
参考处理器模式文档了解:
  • 单一职责处理器 - 每个触发器上下文对应一个处理器类
  • 统一处理器方案 - 包含上下文方法的单一处理器
  • 批量集合策略 - 如何在循环外聚合DML/SOQL操作
  • 最佳实践 - 错误处理、测试边界、部署顺序
选择最适合你触发器复杂度和团队规范的模式。

Step 3: Refactor the Trigger

步骤3:重构触发器

Create the handler class using the appropriate pattern from the reference guide:
  1. Extract logic into handler methods with descriptive names
  2. Implement bulk-safe collections for DML operations
  3. Add proper error handling using try-catch or Database methods
  4. Update the trigger to delegate only, passing Trigger context variables
  5. Preserve behavior - ensure the refactored code produces identical results
The trigger should be reduced to simple delegation:
apex
trigger OpportunityTrigger on Opportunity (before insert, before update, after update) {
    OpportunityTriggerHandler handler = new OpportunityTriggerHandler();
    
    if (Trigger.isBefore && Trigger.isInsert) {
        handler.beforeInsert(Trigger.new);
    }
    
    if (Trigger.isBefore && Trigger.isUpdate) {
        handler.beforeUpdate(Trigger.new, Trigger.oldMap);
    }
    
    if (Trigger.isAfter && Trigger.isUpdate) {
        handler.afterUpdate(Trigger.new, Trigger.oldMap);
    }
}
使用参考指南中的合适模式创建处理器类:
  1. 提取逻辑到具有描述性名称的处理器方法中
  2. 实现批量安全集合用于DML操作
  3. 添加适当的错误处理,使用try-catch或Database方法
  4. 更新触发器仅做委托,传递Trigger上下文变量
  5. 保留原有行为 - 确保重构后的代码产生完全一致的结果
触发器应简化为仅包含委托逻辑:
apex
trigger OpportunityTrigger on Opportunity (before insert, before update, after update) {
    OpportunityTriggerHandler handler = new OpportunityTriggerHandler();
    
    if (Trigger.isBefore && Trigger.isInsert) {
        handler.beforeInsert(Trigger.new);
    }
    
    if (Trigger.isBefore && Trigger.isUpdate) {
        handler.beforeUpdate(Trigger.new, Trigger.oldMap);
    }
    
    if (Trigger.isAfter && Trigger.isUpdate) {
        handler.afterUpdate(Trigger.new, Trigger.oldMap);
    }
}

Step 4: Generate Tests

步骤4:生成测试

Use the test template from
assets/test_template.apex
to scaffold your test class:
  1. Copy the template and rename for your handler
  2. Implement setup methods to create test data
  3. Write unit tests covering each handler method:
    • Positive cases with valid data
    • Negative cases with invalid data
    • Boundary conditions
  4. Add bulk tests with 200+ records to verify bulkification
  5. Test mixed scenarios where only some records qualify for logic
Required test coverage:
  • Each handler method must have at least 2 test methods (positive + negative)
  • At least one bulk test with 200+ records
  • Overall code coverage must be 100%
使用
assets/test_template.apex
中的测试模板搭建你的测试类:
  1. 复制模板并重命名以适配你的处理器
  2. 实现设置方法创建测试数据
  3. 编写单元测试覆盖每个处理器方法:
    • 有效数据的正向用例
    • 无效数据的反向用例
    • 边界条件
  4. 添加批量测试,使用200+条记录验证批量安全性
  5. 测试混合场景,仅部分记录符合逻辑条件
要求的测试覆盖率:
  • 每个处理器方法至少要有2个测试方法(正向+反向)
  • 至少一个使用200+条记录的批量测试
  • 整体代码覆盖率必须达到100%

Step 5: Deploy and Validate

步骤5:部署与验证

Deploy the refactored trigger, handler, and tests:
bash
undefined
部署重构后的触发器、处理器和测试:
bash
undefined

Deploy all components

部署所有组件

sf project deploy start --source-dir force-app/main/default
sf project deploy start --source-dir force-app/main/default

Run tests

运行测试

sf apex test run --class-names OpportunityTriggerHandlerTest --result-format human --code-coverage
sf apex test run --class-names OpportunityTriggerHandlerTest --result-format human --code-coverage

Verify no regressions

验证无回归问题

sf apex test run --test-level RunLocalTests --result-format human

Validation checklist:
- [ ] All new tests pass with 100% coverage
- [ ] No new governor limit warnings in debug logs
- [ ] Existing functionality remains unchanged
- [ ] Deployment to production planned with rollback strategy
sf apex test run --test-level RunLocalTests --result-format human

验证清单:
- [ ] 所有新测试通过且覆盖率达100%
- [ ] 调试日志中无新的 governor limit 警告
- [ ] 现有功能保持不变
- [ ] 已规划生产环境部署及回滚策略

Troubleshooting

故障排除

Issue: Tests fail with "System.LimitException: Too many DML statements"
  • Solution: Ensure handler methods collect DML operations and execute outside loops
Issue: Code coverage below 100%
  • Solution: Add negative test cases and verify all conditional branches are tested
Issue: Behavior differs from original trigger
  • Solution: Review Trigger context variables (new, old, oldMap) are passed correctly to handler
问题:测试失败,提示"System.LimitException: Too many DML statements"
  • 解决方案:确保处理器方法在循环外收集并执行DML操作
问题:代码覆盖率低于100%
  • 解决方案:添加反向测试用例,确保所有条件分支都被测试覆盖
问题:行为与原触发器不一致
  • 解决方案:检查Trigger上下文变量(new、old、oldMap)是否正确传递给处理器

Next Steps

后续步骤

After successful refactoring:
  1. Document the new handler pattern in your team's wiki
  2. Update code review checklist to enforce handler patterns for new triggers
  3. Identify other legacy triggers for refactoring using this skill
  4. Consider implementing a trigger framework if managing many triggers
成功重构后:
  1. 在团队Wiki中记录新的处理器模式
  2. 更新代码审查清单,强制要求新触发器使用处理器模式
  3. 使用此技能识别其他需要重构的遗留触发器
  4. 若管理大量触发器,可考虑实现触发器框架