Trigger Best practices

#1: One Trigger Per Object
A single Apex Trigger is all you need for one particular object. If you develop multiple Triggers for a single object, you have no way of controlling the order of execution if those Triggers can run in the same contexts.

A single Trigger can handle all possible combinations of Trigger contexts which are:

  • before insert
  • after insert
  • before update
  • after update
  • before delete
  • after delete
  • after undelete

So as a best practice, create one Trigger per object and let it handle all of the contexts that you need. Here is an example of a Trigger that implements all possible contexts:

trigger OpportunityTrigger on Opportunity ( before insert, before update, before delete, 
                                       after insert, after update, after delete, after undelete) {
                  // trigger body
}

#2: Bulkify your Helper Methods
Make sure any code that runs a query or DML operation does it in a bulk manner and doesn’t execute within an iteration or a for loop. Executing queries or DML operations within an iteration adds risk that the governor limits will be exceeded. This is also true for any helper or utility methods an Apex request executes.
Governor limits are calculated at runtime. After the request is initiated (Trigger, Visualforce page, etc.), any Apex code executed in that transaction applies and shares the governor limits. So if a trigger uses some Apex methods written in a helper class, it’s important that those shared Apex methods are properly designed to handle bulk records. These methods should be written to be invoked with a set of records, especially if the method has a SOQL query or DML operation.
For example, if the Apex method performs a SOQL query, that method should receive a collection (Array, List, Set, etc.) of records so when it performs the query, it can perform the query for all records in the Apex transaction. Otherwise, if the Apex method is called individually for each record being processed, the Apex transaction will inefficiently run queries and possibly exceed the allowed number of queries allowed in that transaction. The same is true for DML statements in Apex methods.

#3: Logic-less Triggers
Make your Triggers logic-less,  means – the role of the Trigger is just to delegate the logic responsibilities to some other handler class. There are many reasons to do this. For one, testing a Trigger is difficult if all of the application logic is in the trigger itself.

If you write methods in your Triggers, those can’t be exposed for test purposes. You also can’t expose logic to be re-used anywhere else in your org.
Here is an example:
trigger OpportunityTrigger on Opportunity (after insert) {
                       OpportunityTriggerHandler.handleAfterInsert(Trigger.new);
}
Handler class:
public class OpportunityTriggerHandler {
  public static void handleAfterInsert(List opps) {
    // handler logic
  }
}
#4: Avoid SOQL Queries or DML statements inside FOR Loops
A common mistake is that queries or DML statements are placed inside a for loop. There is a governor limit that enforces a maximum number of SOQL queries. There is another that enforces a maximum number of DML statements (insert, update, delete, undelete). When these operations are placed inside a for loop, database operations are invoked once per iteration of the loop making it very easy to reach these governor limits.
Instead, move any database operations outside of for loops. If you need to query, query once, retrieve all the necessary data in a single query, then iterate over the results. If you need to modify the data, batch up data into a list and invoke your DML once on that list of data.

#5: Using Collections, Streamlining Queries, and Efficient For Loops
It is important to use Apex Collections to efficiently query data and store the data in memory. A combination of using collections and streamlining SOQL queries can substantially help writing efficient Apex code and avoid governor limits.

#6: Querying Large Data Sets
The total number of records that can be returned by SOQL queries in a request is 50,000. If returning a large set of queries causes you to exceed your heap limit, then a SOQL query for loop must be used instead. It can process multiple batches of records through the use of internal calls to query and queryMore.

For example, if the results are too large, the syntax below causes a runtime exception:
//A runtime exception is thrown if this query returns enough records to exceed your heap limit.
Account[] accts = [SELECT id FROM account];

Instead, use a SOQL query for loop as in one of the following examples:

// Use this format for efficiency if you are executing DML statements
// within the for loop.  Be careful not to exceed the 150 DML statement limit.

Account[] accts = new Account[];

for (List acct : [SELECT id, name FROM account
                            WHERE name LIKE ‘Acme’]) {
    // Your logic here
    accts.add(acct);
}

update accts;

Let the Force.com platform chunk your large query results into batches of 200 records by using this syntax where the SOQL query is in the for loop definition, and then handle the individual datasets in the for loop logic.

#7: Use of the Limits Apex Methods to Avoid Hitting Governor Limits
Apex has a System class called Limits that lets you output debug messages for each governor limit. There are two versions of every method: the first returns the amount of the resource that has been used in the current context, while the second version contains the word limit and returns the total amount of the resource that is available for that context.

#8 : Use @future Appropriately

It is critical to write your Apex code to efficiently handle bulk or many records at a time. This is also true for asynchronous Apex methods (those annotated with the @future keyword).  Even though Apex written within an asynchronous method gets its own independent set of higher governor limits, it still has governor limits. Additionally, no more than ten @future methods can be invoked within a single Apex transaction.

#9 : Test Methods to Verify Large Datasets

Since Apex code executes in bulk, it is required to have test scenarios to verify that the Apex being tested is designed to handle large datasets and not just single records. An Apex trigger can be invoked either by a data operation from the user interface or by a data operation from the Force.com SOAP API. The API can send multiple records per batch, leading to the trigger being invoked with several records. Therefore, it is key to have test methods that verify that all Apex code is properly designed to handle larger datasets and that it does not exceed governor limits.

#10: Avoid Hardcoding IDs

When deploying Apex code between sandbox and production environments, or installing Force.com AppExchange packages, it is essential to avoid hardcoding IDs in the Apex code. By doing so, if the record IDs change between environments, the logic can dynamically identify the proper data to operate against and not fail.

Here in the below examples we are using Record Type Id, this will work fine in the environment, where code is written but if this code were deployed to  a separate org , there is no guarantee that the record type id will be the same.


for(Account a: Trigger.new){

//Error - hardcoded the record type id
if(a.RecordTypeId=='012500000009WAr'){
//do some logic here.....
}else if(a.RecordTypeId=='0123000000095Km'){
//do some logic here for a different record type...
}
}

Better way to use record type is like below –

//Query for the Account record types
List<RecordType> rtypes = [Select Name, Id From RecordType
where sObjectType='Account' and isActive=true];

//Create a map between the Record Type Name and Id for easy retrieval
Map<String,String> accountRecordTypes = new Map<String,String>{};
for(RecordType rt: rtypes)
accountRecordTypes.put(rt.Name,rt.Id);

for(Account a: Trigger.new){

//Use the Map collection to dynamically retrieve the Record Type Id
//Avoid hardcoding Ids in the Apex code
if(a.RecordTypeId==accountRecordTypes.get('Healthcare')){
//do some logic here.....
}else if(a.RecordTypeId==accountRecordTypes.get('High Tech')){
//do some logic here for a different record type...
}
}

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s