Best Practices for Apex code
1. Bulkify the code
Bulkifying Apex code refers to the concept of making sure the code properly handles more than one record at a time. When a batch of records initiates Apex, a single instance of that Apex code is executed, but it needs to handle all of the records in that given batch. For example, a trigger could be invoked by a Force.com SOAP API call that inserted a batch of records. So if a batch of records invokes the same Apex code, all of those records need to be processed as bulk, in order to write scalable code and avoid hitting governor limits.
Here is an example of written code that only handles one record:
Here is a sample of how to handle all incoming records:
trigger accountTrigger on Account (before insert, before update) {List<String> accountNames = new List<String>{};//Loop through all records in the Trigger.new collectionfor(Account a: Trigger.new){//Concatenate the Name and billingState into the Description fielda.Description = a.Name + ':' + a.BillingState}}
2. Avoid SOQL Queries OR DML statements inside FOR Loops
Here is an example showing both a query and a DML statement inside a for loop:
trigger accountTrigger on Account (before insert, before update) {//For loop to iterate through all the incoming Account recordsfor(Account a: Trigger.new) {/**Since the SOQL Query for related Contacts is within the FOR loop, if this trigger* is initiated with more than 100 records, the trigger will exceed the trigger* governor limit of maximum 100 SOQL Queries.*/List<Contact> contacts = [select id, salutation, firstname, lastname, emailfrom Contact where accountId = :a.Id];for(Contact c: contacts) {c.Description=c.salutation + ' ' + c.firstName + ' ' + c.lastname;/*** Since the UPDATE dml operation is within the FOR loop, if this trigger is initiated* with more than 150 records, the trigger will exceed the trigger governor limit of 150 DML Operations maximum.*/update c;}}}Here is the optimal way to 'bulkify' the code to efficiently query the contacts in a single query and
only perform a single update DML operation.
trigger accountTestTrggr on Account (before insert, before update) {List<Account> accountsWithContacts = [select id, name,(select id, salutation, description,firstname, lastname, email from Contacts)from Account where Id IN :Trigger.newMap.keySet()];List<Contact> contactsToUpdate = new List<Contact>{};// For loop to iterate through all the queried Account recordsfor(Account a: accountsWithContacts){// Use the child relationships dot syntax to access the related Contactsfor(Contact c: a.Contacts){c.Description=c.salutation + ' ' + c.firstName + ' ' + c.lastname;contactsToUpdate.add(c);}}//Now outside the FOR Loop, perform a single Update DML statement.update contactsToUpdate;}
3. Bulkify your Helper Methods4. 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.
Here is a sample that uses collections inefficiently:trigger accountTrigger on Account (before delete, before insert, before update) {//This code inefficiently queries the Opportunity object in two seperate queriesList<Opportunity> opptysClosedLost = [select id, name, closedate, stagenamefrom Opportunity whereaccountId IN :Trigger.newMap.keySet() and StageName='Closed - Lost'];List<Opportunity> opptysClosedWon = [select id, name, closedate, stagenamefrom Opportunity whereaccountId IN :Trigger.newMap.keySet() and StageName='Closed - Won'];for(Account a : Trigger.new){//This code inefficiently has two inner FOR loops//Redundantly processes the List of Opportunity Lostfor(Opportunity o: opptysClosedLost){if(o.accountid == a.id)System.debug('Do more logic here...');}//Redundantly processes the List of Opportunity Wonfor(Opportunity o: opptysClosedWon){if(o.accountid == a.id)System.debug('Do more logic here...');}}}The main issue with the previous snippet is the unnecessary querying of the opportunity records in two separate queries. Use the power of the SOQL
where
clause to query all data needed in a single query. Another issue here is the use of two innerfor
loops that redundantly loop through the list of opportunity records just trying to find the ones related to a specific account. Look at the following revision:trigger accountTrigger on Account (before delete, before insert, before update) {//This code efficiently queries all related Closed Lost and//Closed Won opportunities in a single query.List<Account> accountWithOpptys = [select id, name, (select id, name, closedate,stagename from Opportunities where accountId IN :Trigger.newMap.keySet()and (StageName='Closed - Lost' or StageName = 'Closed - Won'))from Account where Id IN :Trigger.newMap.keySet()];//Loop through Accounts only oncefor(Account a : accountWithOpptys){//Loop through related Opportunities only oncefor(Opportunity o: a.Opportunities){if(o.StageName == 'Closed - Won'){System.debug('Opportunity Closed Won...do some more logic here...');}else if(o.StageName =='Closed - Lost'){System.debug('Opportunity Closed Lost...do some more logic here...');}}}}5. Streamlining Multiple Triggers on the Same Object
It is important to avoid redundancies and inefficiencies when deploying multiple triggers on the same object.
6. Querying Large Data Sets
// A runtime exception is thrown if this query returns enough records to exceed yourheap 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<Account> acct : [SELECT id, name FROM accountWHERE name LIKE 'Acme']) {accts.add(acct);}update accts;7. Use of the Limits Apex Methods to Avoid Hitting Governor Limits
8.Use @future Appropriately
9. Writing Test Methods to Verify Large Datasets
10. Avoid Hardcoding IDsfor(Account a: Trigger.new){//Error - hardcoded the record type idif(a.RecordTypeId=='012500000009WAr'){//do some logic here.....}else if(a.RecordTypeId=='0123000000095Km'){//do some logic here for a different record type...}}Now, to properly handle the dynamic nature of the record type IDs, the following example queries for the record types in the code, stores the dataset in a map collection for easy retrieval, and ultimately avoids any hardcoding.
https://developer.salesforce.com/page/Apex_Code_Best_Practices
//Query for the Account record typesList<RecordType> rtypes = [Select Name, Id From RecordTypewhere sObjectType='Account' and isActive=true];//Create a map between the Record Type Name and Id for easy retrievalMap<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 codeif(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...}}
Comments
Post a Comment