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:

trigger accountTrigger on Account (before insert, before update) {
/**
* This only handles the first record in the Trigger.new collection
* But if more than one Account initiated this trigger, those additional records
* will not be processed
*/
Account acct = Trigger.new[0];
List<Contact> contacts = [select id, salutation, firstname, lastname, email
from Contact where accountId = :acct.Id];
}

    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 collection
for(Account a: Trigger.new){
//Concatenate the Name and billingState into the Description field
a.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 records
for(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, email
from 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 records
for(Account a: accountsWithContacts){
// Use the child relationships dot syntax to access the related Contacts
for(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 Methods

4. 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 queries
List<Opportunity> opptysClosedLost = [select id, name, closedate, stagename
from Opportunity where
accountId IN :Trigger.newMap.keySet() and StageName='Closed - Lost'];
List<Opportunity> opptysClosedWon = [select id, name, closedate, stagename
from Opportunity where
accountId 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 Lost
for(Opportunity o: opptysClosedLost){
if(o.accountid == a.id)
System.debug('Do more logic here...');
}
//Redundantly processes the List of Opportunity Won
for(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 inner for 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 once
for(Account a : accountWithOpptys){
//Loop through related Opportunities only once
for(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 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<Account> acct : [SELECT id, name FROM account
WHERE 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 IDs

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...
}
}

    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.

//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...
}
}
https://developer.salesforce.com/page/Apex_Code_Best_Practices

Comments

Popular posts from this blog

How to extract information from xml using node.js

How to clone a record using custom button in salesforce

salesforce questions and answers