In this blog, I will share the Salesforce Development best practices that I have learned over the years. I have tried to give a few code snippets to provide examples. Feel free to use the code as-is.
One of the key constraints that we all need to deal with is the Governor’s limits. Salesforce applications run on cloud and thus all the Salesforce org utilize shared infrastructure resources and that is the reason Salesforce makes sure the efficient use of the resources. To implement this, it enforces governor limits on the Force.com platform.
Therefore, developing a custom Salesforce applications, keeping governor limits in mind collectively define Salesforce best practices.
DML operation
For data processing operations, such as inserting, updating, deleting, upserting, etc. Salesforce provides DML statements and Database statements. The total number of DML operations allowed in one transaction is 150 as per the governor limit. Therefore, for bulk data processing tasks we should perform DML operation on multiple records at a time using a list of maps.
We should never perform DML operation in for loop because if the number of iterations goes beyond 150 then it will hit governor limit and cause a limit exception.
Exception Handling
1. Exception handling is also a good practice for the visual force page as if we do not handle the exception and an error occurs then the VF page will be redirected to the ugly exception page and if it has formed then we will lose the form field’s values.
2. We should also take care of the null pointer exception which occurs if we access any member variable, method or object field by an object which has not been initialized, and thus it has not been allocated any memory. The typical scenario where it occurs is explained below through apex code:
Try{
List conlist = [SELECT id,lastname FROM Contact WHERE AccountId=abc];
System.debug(number of contacts are +conlist.size());
}
Catch(NullPointerException ex){
System.debug(Null pointer exception occurred +ex.getMessage());
}
Now in the above code contact list has been assigned by SOQL query but if there is no contact record available in org with AccountId value abc then the conlist variable would be null and the code is accessing the list size in debug and therefore it will throw null pointer exception. So it is always recommended to initialize the object or list of object first by calling default constructor such as :
List conlist = new List(); //initializing contact list object
conlist = [SELECT id,lastname FROM Contact WHERE AccountId=abc];
Contact conobj = new Contact();
3. We should always write DML operation in the try-catch block in the Visualforce controller and apex class to find the root cause of the error which has caused the exception and it will help developers to resolve the error. Salesforce provides a specific exception for the specific errors.
It has been explained in detail in below code example:
Try{
List conlist =[SELECT id, Lastname From Contact WHERE Lastname=abc limit 10];
Update conlist;
}
Catch(ListException ex){
System.debug(List exception error +ex.getMessage());
}
Catch(QueryException ex){
System.debug(Query exception has occurred +ex.getMessage());
}
Catch(DMLException ex){
System.debug(Update error has occurred +ex.getMessage());
}
Catch(NullException ex){
System.debug(Null pointer error has occurred +ex.getMessage());
}
SOQL and SOSL
Following are the best practices for using SOQL and SOSL query language:
1. We should never use SOQL and SOSL query inside for loop as the maximum number of SOQL queries allowed in one transaction is 100 for synchronous and 200 in the asynchronous and the maximum number of SOSL queries allowed is 20.
For example: If we want to perform DML operation on child records of a parent object then the typical method used by Salesforce beginners of doing this is given below using the following code:
List acclist = new List();
acclist=[SELECT id,name FROM Account LIMIT 10];
for(Account acc:acclist)
{
List conlist=[SELECT id,Lastname FROM Contact WHERE AccountID=:acc.id];
For(Contact con:conlist){
//perform DML on child contacts
}
}
The above way of doing this is wrong as it uses SOQL inside for loop which may lead to Limit Exception and hit governor limit. The correct way of doing this is explained below:
List acclist = new List();
List conlist = new List();
acclist=[SELECT id,name,( SELECT id,Lastname FROM Contact) FROM Account LIMIT 10];
for(Account acc:acclist)
{
For(Contact con:acc.Contact){
Con.lastname=abc;
Conlist.add(con);
}
}
Update conlist;
This is the correct way of fulfilling this requirement
2. In one transaction one SOQL query can fetch only a maximum of 50,000 records and if it exceeds then it will throw a limit exception. To avoid this, we should use where clause in the query if possible.
3. We should also use indexed fields such as ID, name, phone, email in where clause as it contributes to the Salesforce application performance.
Triggers
Following are the best practices for triggers:
1. For writing triggers, we should ensure that the trigger is short and we should create a separate class (treated as trigger handler) and call that class from the trigger. The separate class can be used to fulfill other business requirements which are similar to the requirement covered by the trigger.
2. While writing update trigger (either before or after) if we have a requirement that the trigger code should only be executed if a specific condition is true i.e only if a particular field changes to a new value then it’s a good practice that we should compare field’s previous value with new value otherwise the trigger code would be executed on every update operation which is not appropriate. Example given below:
Trigger accounttrigger on Account(after update){
For(Account acc:trigger.new){
If(trigger.oldMap.get(acc.id).name!= trigger.newMap.get(acc.id).name){
//execute the trigger code here
}
}
}
3. If we have the requirement to update a field based on some criteria during insertion or update operation, then we should write before trigger because if we try to accomplish this task in after trigger then it will lead to trigger looping.
For example:
Trigger Accounttrigger on Account(after update){
List acclist = new List();
For(Account acc:trigger.new){
If(acc.name=abc){
Acc.name=xyz;
Acclist.add(acc);
}
}
Update acclist;
}
The above approach will cause trigger looping as it is updating Account list in after trigger means after the Account list has been updated and thus it will cause the above trigger to fire again.
The following code sample is correct for this scenario:
Trigger Accounttrigger on Account(before update){
List acclist = new List();
For(Account acc:trigger.new){
If(acc.name=abc){
Acc.name=xyz;
}
}
}
4. We should avoid calling the future within the future. For example, if we have a trigger handler which is having a future method and as per the requirement, we have to execute DML operation, and then it will lead the same trigger to be executed again which then might call the future method again, which will throw an asynchronous exception.
Visualforce pages: In the Visualforce controller, we should take care of ViewState memory which is 135KB.
View State: If we have a form in the VF page then the form fields values are stored in ViewState in an encrypted format to maintain the state of the page.
Following are the factors which contribute to ViewState memory:
a. If we have some variables which we do not need to use in VF page UI then we should declare it as transient variable otherwise it will add to the view state memory and may lead to view state error
b. In the VF page, we should try to keep the minimum number of forms
c. If we want to display a list of object records on the VF page, then we should use where clause on indexed fields in SOQL query.
Future methods: While writing the apex code which contains future methods we should pay special attention to its governor limits. In one apex invocation, no more than 10 future calls are allowed and not more than 200 future calls per Salesforce license per 24 hours are allowed. Following is the example of inefficient apex code for the future method:
Trigger testtrigger on Account (after insert){
For(Account acc:trigger.new){
Asynchapexclass.testfuture(acc.id);
}
}
Global class asynchapexclass{
@future
Public static void testfuture(ID acc_id){
List conlist=new List();
Conlist=[SELECT id,lastname FROM Contact WHERE AccountId:=acc_id];
For(Contact c:conlist){
c.lastname=abc;
}
Try{
Update conlist;
}
Catch(DMLException ex){
System.debug(update error +ex.getMessage());
}
}
}
The above code is inefficient because it is calling future method within for loop in the trigger and therefore it is more likely to hit governor limit of a maximum number of future calls allowed in one transaction which is 10. Following code is efficient and appropriate for this kind of requirement:
Trigger testtrigger on Account (after insert){
Asynchapexclass.testfuture(trigger.newMap.keyset());
}
Global class asynchapexclass{
@future
Public static void testfuture(Set accid_set){
List conlist=new List();
Conlist=[SELECT id,lastname FROM Contact WHERE AccountId IN :accid_set];
For(Contact c:conlist){
c.lastname=abc;
}
Try{
Update conlist;
}
Catch(DMLException ex){
System.debug(update error +ex.getMessage());
}
}
}
Test Class
While creating test class we should not use org data but rather we should create test data. To create test data we should create a specific method with the annotation @Testsetup and create test data and then the test data(created in @testsetup method) would be available in all the other methods existing in the test class. Therefore, we do not have to create separate test data for every method existing in the test class.
Leave A Comment