Salesforce Best Practices
Do not write SOQL and DML Operations inside loop statements:
A common mistake that developers do, is to place queries or DML statements inside a “for loop”. Salesforce has Governor limits that enforce a maximum number of SOQL Statements. Placing queries inside the loop can increase the chances of hitting the limits.
Not Recommended Eg:
for(Account acc:Trigger.New){
for(Contact Con:[Select id form Contact where AccountId=:acc.Id])) {
//logic code
}
}
- If the loop runs for more than 100 times it will return with error ‘Too many SOQL queries issued: 101’
- Similarly avoid any DML operations inside a loop to avoid ‘Too many DML operations issue’.
Best Practice:
- Move SOQL/DML out of loops.
- Query: If you need query results, get all the records using a single query and iterate over the resultset.
Example of putting SOQL Query outside of for loop:
Map<Id, Account> accountMap=new Map<id, Account>([select id,name, from account]);
for(Account acc: accountMap ()){
//logic code
}
Bulkify Apex Code:-
Bulkifying Apex code refers to the concept of making sure the code properly handles more than one record at a time.
// NOT Recommended
trigger accountTestTrggr on Account (before insert, before update) {
// This only handles the first record in the Trigger.new collection
Account acct = Trigger.new[0]; // first record
List<contact> contacts = [select id, salutation, firstname, lastname, email from Contact where accountId = :acct.Id];
}
In the above Trigger, the code explicitly accesses only the first record in the trigger.new collection by using the syntax Trigger.New[0]. Instead, the trigger should properly handle the entire collection of Accounts using Trigger.new collection.
Trigger AccountTrigger on Account(before insert){
for(Account acc: trigger.New) // we are now taking care of the all the records in the collection Trigger.new
{
if(acc.Name != null){
// Logic Code
}
}
}
BULKIFY YOUR HELPER METHODS:-
- Make sure that the helper 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
- Use the power of the SOQL where clause to query all data needed in a single query
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.
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, in that case, the SOQL query for loop must be used.
The loop can process multiple batches of records through the use of internal calls to query and queryMore
Here is an example of a SOQL query for loop,
for( List<Account> accList: [select id, name from Account where BillingCountry LIKE ‘%USA%’]) {
// add your logic here.
}
Avoid Hard-coded 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.
If we wish to utilize record types, instead we can reference them via their DeveloperName which will be consistent across environments.
Id accountRTId=’xxxxxxxxxx’;
This will ensure that code can be deployed safely to different environments. Try to get the record Type Id Using Schema methods get the Record Type Id of the sobject.
accountRTId=
Schema.sobjectType.Account.getRecordTypeInfosByName().get(‘RTName’).getRecordTypeId();
Use of custom label/custom settings/custom metadata to avoid hard coding IDs.
Custom Label: Custom labels are custom text values accessed from Flows, Lightning components, Apex classes, and Visualforce pages. While deploying a custom label, the data of that custom label also gets deployed. update the custom label value post-deployment to avoid unexpected behavior in your code.
Custom Setting: Custom Settings are variables you can use in your code but modify and set outside your code. They are cached. It is especially useful to store information often accessed from Salesforce flow or Apex code, as it will perform must better than a custom object. Custom Settings do not count against SOQL limits when it’s fetched. when you deploy them, it does not bring the data over.
Custom Metadata Type: Custom Metadata Types are like Custom Settings. its data also gets deployed, while deploying a custom metadata type.
Exception Handling In Apex Code:-
Whenever you are writing Apex Code, there are a few important considerations:
DML statements return run-time exceptions if something went wrong in the database during the execution of the DML operations. Don’t forget to use Try catch blocks for exception handling. With Apex, you can write code that responds to specific exceptions.
Syntax:
try{
// Apex Code
}Catch(Exception e){
}
Avoid Nesting Loops Within Loops:
Nested loops should be avoided in Apex controllers as they may slow down page processing or may hit the governing limits for the page.
Another easy way that gives a nice structure to make the inner loop a different function, or to reduce the use of loops completely.
We usually encounter code when we have two or three or more barriers generated that affect performance. An easy way to avoid nesting traps is to use Maps.
for(contact con: myContacts){
for(Opportunity opp: myopp){
if(opp.StageName = ‘Closed Won’){
//Logic
} elseif(opp.StageName =’ Closed Lost’){
//Logic
}
}
}
For example, you can find or create a key for each second loop item and enter a key and value on the map.
Rather than using nested loops, a good strategy is to abstract your logic into separate methods (which perform the logic themselves). This way, when we’re looking at a block of code from a higher level, it is far easier to understand. Abstracting our code out like this also has other benefits, such as making it easier to test specific aspects of our code.
Example
for(Account acc: myAccounts){
calopportunityRatings(acc.Opportunities);
}
Naming Convention:
The code is written once but read many times by others on the project team and even those from other teams. As a result, readability is essential. Readability is simply figuring out what the code does in less time. Every project or team can have its own naming conventions.
Class Naming Convention:(Pascal Case)
public class UpdateAccount{
// Code Logic
}
Method Naming Convention:(Camel Case)
public class Foot {
public void instMethod() {
}
}
Local Variable Conventions:(Camel Case)
public class Foot {
public void instMethod() {
Integer instancerow;
}
}
Constants:
The names of variables declared class constants should be all uppercase with words separated by underscores (“_”). All uppercase letters in this format :
private static String CONTACT_LIMIT =’10’;
Visualforce Pages Naming Conventions:-
- Names and Labels Should be unique and begin with an uppercase letter.
- It Should not contain UnderScores or Spaces.
- The words should be concatenated with Initial uppercase and subsequent internal words capitalized.
- Whole words should be used and use of acronyms and abbreviations should be limited.
- It Should be in PascalCase,
Example:
ContactClone
Lightning Web Components Naming Convention:-
In HTML:
Use camel case to name your component and use kebab-case to reference a component in the markup.
JavaScript File :
Java Script Class name should be in PascalCase, Example HelloWorld.
CSS File:
- Needs to be created in the component bundle
- Has the same name as the component
- Uses standard CSS syntax
- Whole words should be used and use of acronyms and abbreviations should be limited. Name should be camelCase, Example showCaseDetail();
Lightning Component Naming Conventions:
Lightning component names must be unique. They should begin with a lowercase letter. All components should end with the suffix “Cmp”.
userCardCmp (Initial lower case letter and suffixed with “Cmp”)
Lightning Events:
Lightning event names must be unique. They should begin with a lowercase letter. All events should end with the suffix “Evt”.
userEvt (Initial lowercase letter and suffixed with “Evt” ).
Avoid Returning JSON to Lightning Components:-
@AuraEnable methods for our Lightning Components, we frequently need to return more complex data structures, such as records, custom data types, or lists of these types.It would be to serialize these objects into JSON, and then deserialize them within our component’s JavaScript.
@AuraEnabled(cacheable=true)
public static String getContacts(){
return JSON.serialize([Select Id, LastName from Contact]);
}
This approach is actually an anti-pattern and can lead to some poor performance within our components. Instead, we should be directly returning these objects and letting the platform handle the rest for us.
@AuraEnabled(cacheable=true)
public static List<Contacts> getContacts(){
return [Select Id, LastName from Contact];
}
Converting our returned data into JSON results in us consuming large amounts of heap memory and spending many CPU cycles converting these objects into a lovely and long string.
When we directly return our result, the serialization into JSON handled by the platform, outside of our governor limits. The result is also automatically converted back into objects for our components to consume, without having to perform as many expensive JSON.parse() operations.
Make reusability of Code
The best practice for the Apex Code is to write a code that can be reused in another application or module. This includes rendering the code generic so that it can be reused with minimal modifications.
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.
Heap limit exception:
There are several ways around the heap limit exception. One way is to use a SOQL query for loop.
Account[] accts = new Account[];
for (List<Account> acct : [SELECT id, name FROM account WHERE name LIKE ‘Xyz’]) {
// Your logic here
accts.add(acct);
}
Apex SOQL injection:
To prevent a SOQL injection attack, avoid using dynamic SOQL queries. Instead, use static queries and binding variables.
Not Recommended:
public class SOQLController {
public String name {
get { return name;}
set { name = value;}
}
public PageReference query() {
String qryString = 'SELECT Id FROM Contact WHERE ' +
'(IsDeleted = false and Name like \'%' + name + '%\')';
queryResult = Database.query(qryString);
return null;
}
}
Best Practice
public class SOQLController {
public String name {
get { return name;}
set { name = value;}
}
public PageReference query() {
String queryName = '%' + name + '%';
queryResult = [SELECT Id FROM Account WHERE
(IsDeleted = false and Name like :queryName)];
return null;
}
}
Governor Limits of @Future Method:
- The maximum number of calls allowed per Apex transaction is 10.
- The maximum number of calls per Salesforce license within 24 hours is 200.
- This method requires that all the parameters are simple values or arrays of simple values or collections of simple values.
- Methods with future annotation do not take sObjects or other objects as arguments.
- Methods that are annotated as @future cannot be used in Visualforce controllers, in either getMethodName, setMethodName or the constructor.
Points To Remember:
- It is a good practice when creating a class is to use the “with sharing” keyword. This keeps the instances of the class accessible from outside of the class.
- You should use filters in SOQL queries to limit the number of records returned by the query. If you don’t, the query will return too many results which may cause it to exceed the limit.
- Use Database Methods while doing DML operation\
- When you’re running Apex, it’s easy to miss a governor limit, but with the Limits Apex Method, you can avoid reaching them in your code. Before executing, check whether the code exceeds any governor limits. Here is an example showing how to check for governor limits
if (Contacts.size() + Limits.getDMLRows() > Limits.getLimitDMLRows()) {
//logic here
}
Test class:
- Apex Test classes are an essential part of Salesforce development,as they help to ensure that your code is functioning correctly or not.
- Also, at least 75% of our apex code must be covered in order to successfully deploy the code to the production organization.
- Instead, you should make sure that every use case of your application is covered, including positive and negative cases, as well as bulk and single records.
Test Method Syntax
@isTest static void testName() {
// code_block
}
Alternatively, a test method can have this syntax:
static testMethod void testName() {
// code_block
}
Annotations:
- @isTest must be used before starting the test class if the class version is more than 25.
- To define the test method, you can either use @isTest or testMethod keyword with method.
- Always use @testSetup method dedicated to create test records for that class. This is newly added annotation and very powerful.
- Use Test.startTest() to reset Governor limits in Test methods.
- No SeeAllData=true Always use ( seeAllData = false ) at class or method level. User, profile, organization, AsyncApexjob, Corntrigger, RecordType, ApexClass, ApexComponent, ApexPage we can access without (seeAllData=true)
- If you are doing any Asynchronous operation in code, then do not forget to call Test.stopTest() to make sure that operation is completed.
- Use System.runAs() method to enforce OWD and Profile related testings. This is very important from Security point of View. System.runAs() will not enforce user permission or field level permission.
- @TestVisible annotation is used to make private methods visible inside test classes.
- Use As much as Assertions like System.AssertEquals or System.AssertNotEquals.
- Always try to pass null values in every method.
- Please use call out mock to test web-service call out.
Use System.assert() statements:
- The System.assert() method is used to check if a particular condition is true or false in your Apex code.
- It allows you to check that your code is functioning as expected.
- You can use System.assert() to check that a variable is not null, or the method is returning the correct value.
Example:
@isTest
public class MyTestClass {
static testMethod void testMyMethod() {
Account a = new Account(Name='Test Account');
insert a;
System.assertEquals('Test Account', a.Name);
}
}
Use Test.startTest() and Test.stopTest()
- The Test.startTest() and Test.stopTest() methods are used to define a block of code that should be run in a separate test context.
- This is useful for testing code that makes use of governor limits, such as the number of records that can be queried in a single transaction.
- By using these methods, you can ensure that your tests will not exceed these limits, and that your code will function correctly in a production environment.
Example:
@isTest
public class MyTestClass {
static testMethod void testMyMethod() {
Test.startTest();
// code that uses governor limits
Test.stopTest();
}
}
Use @testSetup
- The @testSetup annotation is used to define a method that will be run before all test methods in the class.
- This is useful for setting up test data that is needed by multiple test methods.
- By using the @testSetup annotation, you can ensure that your test data is created only once, which can improve the performance of your tests.
Example:
@isTest
public class MyTestClass {
@testSetup static void setup() {
Account a = new Account(Name='Test Account');
insert a;
}
static testMethod void testMyMethod() {
Account a = [SELECT Name FROM Account WHERE Name='Test Account'];
System.assertEquals('Test Account', a.Name);
}
}
Use the @testVisible annotation
- The @testVisible annotation is used to make an Apex class or method visible to test classes, even if it is defined as private.
- This is useful for testing methods or classes that are not accessible to test classes by default.
- By using the @testVisible annotation, you can ensure that your tests have access to the necessary methods and classes for testing.
Example:
public class MyClass {
@testVisible private static Integer myMethod() {
return 5;
}
}
@isTest
public class MyTestClass {
static testMethod void testMyMethod() {
Integer result = MyClass.myMethod();
System.assertEquals(5, result);
}
}
Use the Test.loadData() method
- The Test.loadData() method is used to load test data from a CSV file into your test class.
- This is useful for creating large amounts of test data, as it allows you to easily import data from a spreadsheet or other external source.
Example
@isTest
public class MyTestClass {
static testMethod void testMyMethod() {
List<sObject> records = Test.loadData(Account.sObjectType, 'test_accounts');
System.assertEquals(10,records.size());
}
}
Use @isTest(SeeAllData=false)
- The SeeAllData parameter can be used to specify whether or not the test class or method can access all data in the organization, or just data that is created by the test itself.
- By setting SeeAllData to false, you can ensure that your tests are not affected by data that is not related to the test, and that your tests will be more reliable.
Example
@isTest(SeeAllData=false)
public class MyTestClass {
static testMethod void testMyMethod() {
Account a = new Account(Name='Test Account');
insert a;
System.assertEquals('Test Account', a.Name);
}
}
Use the Test.setFixedSearchResults() method
- The Test.setFixedSearchResults() method is used to set the search results that will be returned by a search query in a test class.
- This is useful for testing code that makes use of search queries, as it allows you to control the data that is returned by the query.
- By using the Test.setFixedSearchResults() method, you can ensure that your tests are not affected by changes in the data that is returned by the query.
Example:
@isTest
public class MyTestClass {
static testMethod void testMyMethod() {
List<Account> accounts = new List<Account>{
new Account(Name='Test Account 1'),
new Account(Name='Test Account 2')
};
insert accounts;
Test.setFixedSearchResults(accounts);
List<Account> results = [FIND 'Test Account*' IN ALL FIELDS RETURNING Account(Name)];
System.assertEquals(2, results.size());
}
}
Bulkify Your Code:
Bulkifying your code becomes especially important when importing or inserting more than one record,so that your code properly handles all the records in different contexts.
Triggers
- Apex Code can be executed from the Triggers.
- And Triggers are executed before or after the Salesforce Records are created, updated, deleted, or undeleted.
- Hence, it is absolutely necessary to follow the Best Practices while writing an Apex Trigger.
One Trigger Per Object
- Always prefer to have one trigger per object. In case of multiple triggers per object, you don’t have control over the order of execution for triggers.
Naming Convention
- Decide the appropriate name for your Trigger.
- As Trigger is related to Object, the name of the Trigger should be Object-specific and should follow the same naming convention for all the Triggers in the Org.
- For Example, it should be something like AccountTrigger for Account , CaseTrigger for Case respectively.
Use only Required Events in Trigger
Add only required Events in Trigger. For example, if you want to execute the Trigger after inserting an Account record, only add after insert event like below:
trigger AccountTrigger on Account (after insert) {
//Trigger Logic.
}
Context-Specific Handler Methods
- Create context-specific handler methods in Trigger handlers like afterInsertLogic, beforeInsertLogic etc.
- We should have different handler methods for each Trigger Context. It improves the code readability. It also helps other developers to edit the Triggers easily.
Bulkify your Code
- Make sure the Trigger handles multiple records at a time.
- Always write the Trigger logic considering the fact that multiple records can be inserted, updated, or deleted at a time.
Bulkfication Example:
//Not Recommended
// Remember that up to 200 records can be in Trigger.new
for (Opportunity opp : Trigger.new) {
Task t = new Task();
t.Name = 'Give your prospect a free t-shirt';
t.WhatId = opp.Id;
insert t; // You'll get an error after the 150th opp!
}
//Recommended
// Do an insert DML on all tasks at once using a List
List<Task> taskList = new List<Task>();
for (Opportunity opp : Trigger.new) {
Task t = new Task();
t.Name = 'Give your prospect a free t-shirt';
t.WhatId = opp.Id;
taskList.add(t);
}
insert taskList; // Notice this is outside the loop
Logic-less Triggers
- Logic-less Triggers means that we should not write any Business logic in the Triggers.
- All the Business Logic should be written in separate Apex Class which is know as Trigger Handler in this context.
- This class can have multiple methods to handle the Business Logic. And, we should only call these methods from the Trigger.
Use @future Appropriately
- It is essential to write down your Apex code with efficiency handle bulk or many records at a time.
- Use @fututre just in case of a call out from trigger to make sure the current transaction should not have to wait for a call out response.
Avoid calling batch Execution from Trigger
When a batch is called from Trigger there are high chances to hit the governor limit provided by Salesforce.
Use Custom Trigger Framework
- We can use custom trigger frameworks to properly maintain triggers on all objects.
- We can enable or disable the triggers on particular objects depending on the business situation, for example, while performing bulk data uploads.
- Using trigger frameworks, we can also enable and disable the triggers using Custom Settings as well if needed. This gives us a lot of control on triggers.
Consider Process Builder and Flows before writing Trigger
- Most of the small Triggers can be replaced with Process Builders.
- Even the complex Triggers can be replaced with the combination of Process Builder and Flow.
- Hence, before writing a Trigger, see if you can implement the same functionality with Process Builder and Flows.