Saturday, June 1, 2019

Apex Code Best Practices

Here are the best Practices we should follow while writing apex code.

1) Bulkify the Code.


Bulkify here simply means writing code that handles more than one record at a time.

Here is an example of poorly written code that handles only one record at a time.

trigger contactTrigger on contact (before insert, before update) {

   //This only handles the first record in the Trigger.new collection, other records are not processed

   Contact con= Trigger.new[0];

   List<Contact> conList = [select id, firstname, lastname 

              from Contact where id= :con.Id];

}

Here is an example of code written to handle bulkification,

trigger contactTrigger on contact (before insert, before update) {

 List<Contact> conList =new List<Contact>();
   for(contact con:trigger.new)
       {
if(con.firstName="Test")
            {   con.booleanField=true;
    conList.add(con);
            }

       }

   update conList;
             

}

2) Avoid SOQL Queries or DML statements inside FOR Loops.


This is to take Governor Limit in consideration,

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. 

SOQL queries per transaction.
==>100.(synchronous)
SOQL queries per transaction.
==>200(asynchronous)
dml statements per transaction
==> 150

3) Use single trigger on object.


If we have multiple trigger on one object we cannot control the order of execution.

4) Querying object having more than 50,000 records.

The total number of records that can be queried by soql queries is 50,000 record. If we query more than 50,000 record we exceeds the heap limit.

//A runtime exception is thrown if this query returns enough records to exceed your heap limit.

Account[] accts = [SELECT id FROM account];

To avoid this we should use SOQL query for loop it can process multiple batches of record using call to query and query more.

for (List<Account> acct : [SELECT id, name FROM account

                            WHERE name LIKE 'Test']) {
    // Your logic here

}


5) Avoid hardcoding id's in apex code.

Instead we should use label or some other way to avoid hardcoding id's as these will results in improper processing on records if sandbox and production environment have different id's for the match we are doing in code.

Simple example below to match record type,

  List<RecordType> rtypes = [Select Name, Id From RecordType

                  where sObjectType='Contact' and isActive=true];

     Map<String,String> contactRecordTypes= new Map<String,String>{};

     for(RecordType rt: rtypes)
{

        contactRecordTypes.put(rt.Name,rt.Id);

        }

      for(contact a: Trigger.new)
  {

          if(a.RecordTypeId==contactRecordTypes.get('someRecordTypeName')){            

             
          }

     }


6) Use of the Limits Apex Methods

Apex has a system class called Limits that let us identify the amount of resources used and amount of resource available to avoid hitting governor limit in a particular context.

System.debug('Total Number of SOQL Queries allowed in this Apex code context: ' +  Limits.getLimitQueries());

System.debug('Total Number of records that can be queried  in this Apex code context: ' +  Limits.getLimitDmlRows());

System.debug('Total Number of DML statements allowed in this Apex code context: ' +  Limits.getLimitDmlStatements() );

System.debug('Total Number of CPU usage time (in ms) allowed in this Apex code context: ' +  Limits.getLimitCpuTime());

System.debug('1. Number of Queries used in this Apex code so far: ' + Limits.getQueries());

System.debug('2. Number of rows queried in this Apex code so far: ' + Limits.getDmlRows());

System.debug('3. Number of DML statements used so far: ' +  Limits.getDmlStatements());  
System.debug('4. Amount of CPU time (in ms) used so far: ' + Limits.getCpuTime());

System.debug('Final heap size: ' +  Limits.getHeapSize());

7) Using @future

  • Methods with the future annotation cannot be used in Visualforce controllers in either getMethodName or setMethodName methods, nor in the constructor.
  • Methods with the future annotation cannot take sObjects or objects as arguments it always takes id's as argument.

Trigger:

trigger accountAsyncTrigger on Account (after insert, after update) {
  for(Account a: Trigger.new){
    // Invoke the @future method for each Account
    // This is inefficient and will easily exceed the governor limit of
    // at most 50 @future invocation per Apex transaction
    someClass.processAccount(a.id);
   }    
}

Apex:

global class someClass{


  @future
  public static void processAccount(Id accountId) {
// some code here
         }
}


Best way for future method invocation,

trigger accountAsyncTrigger on Account (after insert, after update) {
    //By passing the @future method a set of Ids, it only needs to be
    //invoked once to handle all of the data.
    someClass.processAccount(Trigger.newMap.keySet());
}

3 comments: