Review and Refactor
By the end of this lesson, you'll be able to:
- Review the complete feature across trigger, handler, and service class
- Identify any remaining duplication or unclear naming
- Confirm the final structure matches Module 24's trigger framework and Module 14's clean-code habits
Prerequisites: "Test the Feature"
The finished feature, end to end
trigger OpportunityTrigger on Opportunity (before update, after update) {
OpportunityTriggerHandler handler = new OpportunityTriggerHandler();
if (Trigger.isBefore) {
handler.beforeUpdate(Trigger.new, Trigger.oldMap);
}
if (Trigger.isAfter) {
handler.afterUpdate(Trigger.new, Trigger.oldMap);
}
}
trigger OpportunityTrigger on Opportunity (before update, after update) {— routes both events.OpportunityTriggerHandler handler = new OpportunityTriggerHandler();— one handler instance.if (Trigger.isBefore) {— the validation path.handler.beforeUpdate(Trigger.new, Trigger.oldMap);— blocks invalid saves.}— closes theif.if (Trigger.isAfter) {— the rollup path.handler.afterUpdate(Trigger.new, Trigger.oldMap);— detects changes and delegates toAccountRollupService.}— closes theif.}— closes the trigger.
Three pieces, each with one clear job: OpportunityTrigger (routes events), OpportunityTriggerHandler (detects what changed and by how much), AccountRollupService (applies the adjustment). This mirrors every project module's closing lesson — a system that works, then reviewed once more with fresh eyes.
Checking against this course's standards
- Bulk-safe? Yes — exactly one query and one DML statement in the rollup path, regardless of batch size (Module 25).
- Thin trigger? Yes — the trigger body only routes to handler methods (Module 24).
- Single Responsibility? Yes — three classes, three distinct jobs (Module 14).
- Validated? Yes — a Closed Won Opportunity can't save without a positive Amount (this module, Lesson 4).
- Verified? Yes, manually — formal automated tests are still ahead, in Module 31.
One more refactor: a shared constant
public class OpportunityTriggerHandler {
private static final String CLOSED_WON = 'Closed Won';
public void afterUpdate(List<Opportunity> newOpportunities, Map<Id, Opportunity> oldMap) {
for (Opportunity opp : newOpportunities) {
Opportunity oldOpp = oldMap.get(opp.Id);
Boolean justWon = opp.StageName == CLOSED_WON && oldOpp.StageName != CLOSED_WON;
// ...
}
}
}
public class OpportunityTriggerHandler {— the handler class.private static final String CLOSED_WON = 'Closed Won';— the literal string'Closed Won'appeared in three separate places across this module's code so far — a classic candidate for Module 13'sfinalkeyword.- (blank line)
public void afterUpdate(List<Opportunity> newOpportunities, Map<Id, Opportunity> oldMap) {— the method.for (Opportunity opp : newOpportunities) {— loops as before.Opportunity oldOpp = oldMap.get(opp.Id);— the prior version.Boolean justWon = opp.StageName == CLOSED_WON && oldOpp.StageName != CLOSED_WON;— one named constant used in place of the repeated literal — changed in exactly one place if the stage name ever needs to change.// ...— the rest of the method, unchanged.}— closes the loop.}— closes the method.}— closes the class.
Exercise
Refactor this method to use a private static final String constant named MINIMUM_AMOUNT_ERROR instead of the repeated literal message.
Show hint
private static final String MINIMUM_AMOUNT_ERROR = '...';
Review and Refactor Quiz
My Notes
Log in to keep private notes on this lesson.
Questions about this lesson
No questions yet — be the first to ask.
Log in to ask a question about this lesson.
Summary
The final lesson steps back and reviews the whole feature — trigger, handler, and service class together — checking it against everything this course has taught about clean, bulk-safe, well-structured Apex.