Tuesday, December 20, 2022

Apex Code Best Practices

In this post, we will talk about Salesforce apex best practices. Apex code is used to write custom and robust business logic. As with any language, there are key coding principles and best practices that will help you write efficient, scalable code. In this post, we will talk about Apex Code’s best practices for writing code on the Salesforce platform. We will also learn how to resolve Apex limitations with Apex Code Best Practices.

Why we need Apex Code best practices

Best practice recommended by Salesforce because apex run in a multitenant environment, the Apex runtime engine strictly enforces a number of Salesforce Governor limits to ensure that runways apex code does not monopolize shared resources.

Let’s talk about some examples of Salesforce apex code best practices.

1. Bulkify Apex Code

The very first principle is to write code for more than one record at a time. We should write scalable code and avoid hitting governor. Let understand with example. In below code we are using hard Coded Index[0]. Means code will only work on single record

Trigger AccountTrigger on Account(before insert){
  Account acc= Trigger. New[0];
  if(acc.Name != null){
    // DO someything
  }
}

Solution

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){
    if(acc.Name != null){
      // DO someything
    }
  }
}

2. Avoid SOQL & DML inside for Loop

Do not place SOQL or DML(insert/update/delete/undelete) statements inside a loop. When these operations are placed inside a for loop, database operations are invoked once per iteration of the loop making it very easy to reach these SFDC governor limits.

For(Account acc: Trigger.new){
 for(Contact con:[select id from contact where accountId = :acc.Id]){
 }
}

Solution

  1. Move SOQL/DML out of loops
  2. Query: If you need query results, get all the records using a single query and iterate over the resultset
  3. Update: If you need to update, batch up the data into a collection and invoke DML once for that collection

Here is an example of putting SOQL Query outside of for loop.

Map<Id, Account> accountMap=new Map<id, Account>([select id,name, 
(select id from contacts) from account where id in:trigger.newmap.keyset()]);

for(Account acc: accountMap.values()){
	For(Contact con:acc.Contacts){ 
	}
}

3. Querying Large Data Sets

In Salesforce we have a governance limit that SOQL queries can return 50,000 records. If you are working on large data sets which cause exceed the heap size limit, in that case, the SOQL query for loop must be used. Here is an example of a SOQL query for loop as in one of the following examples.

Solution : Use a SOQL query for loop.

for( List<Account> accList: [select id, name from Account where BillingCountry
  LIKE ‘%United%’]) {
	// add your logic here.
}

Check this post to learn about SOQL Best Practices. Here are best practices

  1. Building Efficient & Selective Queries
  2. avoid Common Causes of Non-Selective SOQL Queries
  3. use Query Optimizer
  4. Avoiding querying on formula fields
  5. Custom Indexes Containing null Rows
  6. avoid SOQL injection.

Optimize SOQL Queries to avoid Timeout Issues

  • While Querying unnecessary records and fields, the transaction will take additional time to retrieve the result from the SOQL query. This might lead to Timeout issues.
  • Sometimes, these small mistakes lead to issues that can take a lot of time and effort in debugging and fixing. 
  • As an Apex coding best practice,  In SOQL queries, avoid querying columns that are not used in the code – e.g. In a process, if the need is only the LastName of the contact, query only the last name, there is no need to query all the fields from the contact record.
  • Similarly, query the required number of records using right filters and joins so that unwanted records are not retrieved

4. Use of Map of Sobject

In a few cases, we need to get the value of records from different sobject based on looped sobject records. In that case, we can use Map of Sobjects to get the values and avoid SOQL queries in for loop.

Solution

  1. Use Apex Collections to efficiently query data and store the data in memory
  2. A combination of using collections and streamlining SOQL queries can substantially help writing efficient Apex code and avoid governor limits
Map<Id, Account> accountMap=new Map<Id, Account> ([select id, 
name from Account where name like ‘%Inc.%’]);

for(Contact con: [select id,AccountId, name from Contact 
where AccountId in:accountMap.keyset()]{
	//Get Related Account data using Contact field
	Account acc=accountMap.get(con.AccountId);
}

5. Use of the Limits Apex Methods

Use Apex Limits Methods to Avoid Hitting SF Governor Limits. Many of us facing governor limit error in trigger/classes/test classes. Few of the governor limit errors as follow:-

  1. Too many SOQL queries: 101
  2. Dml rows 10001
  3. too many query rows 50001.

Here is the snippet to how to use Apex limit Apex methods.

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

Account acc=[select id, name from Account where BillingCity!=null  
order by createdDate desc limit 1];

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

Now, using the above Limit methods we can check how many SOQL queries we can issue in the current Apex Context

If(Limits.getLimitQueries() - Limits.getQueries()>0) {
  // Execute SOQL Query here.
}
  1. System class Limits – use this to output debug message about each governor limit – 2 version
  2. First: (e.g. Limits.getHeapSize()) returns the amount of the resource that has been used in the current context.
  3. Second: Contains the word limit (e.g. Limits.getLimitDmlStatements()) and returns the total amount of the resource that is available for that context.
  4. Use in the Apex code directly to throw error messages before reaching a governor limit.
  5. When an end-user invokes Apex code that surpasses more than 50% of any governor limit, you can specify a user in your organization to receive an email notification of the event with additional details.

6. Avoid Hardcoding IDs

Don’t hard code Id in Apex Code. When you deploying apex code between sandbox and production it may failed because id may not same. For example for putting recordType Id.

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.

Id accountRTId= Schema.sobjectType.Account.getRecordTypeInfosByName().get(‘RTName’)
.getRecordTypeId();

Use of custom settings/custom metadata to avoid hard coding IDs. For example, at times you need to handle login credentials in your code. To avoid this, it is an Apex coding best practice to create custom settings or custom metadata where one can store the credentials and configuration values and retrieve those values dynamically inside the apex.

7. Use Database Methods while doing DML operation

An Apex transaction represents a set of operations that are executed as a single unit. All DML operations in a transaction either complete successfully or if an error occurs in one operation, the entire transaction is rolled back and no data is committed to the database. Apex gives you the ability to generate a savepoint, that is, a point in the request that specifies the state of the database at that time.

By using the Database class method, you can specify whether or not to allow for partial record processing if errors are encountered.

Database.SaveResult[] accountResults=Database.Insert(accountsToBeInsert, false);
// Using above code, we can work on failure records
  For(Database.SaveResult sr:accountResults) {
	If(!sr.isSuccess()) {
	// add your logic here 
	for(Database.Error err : sr.getErrors()) {
	}
  }
}

8. Exception Handling in Apex Code

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.

try{
	// Apex Code 
}Catch(Exception e){
}

9. Write One Trigger per Object per event

Apex triggers within Salesforce are designed to help you automate certain tasks. Apex triggers allow you to perform custom actions before and after events in Salesforce. While writing Apex Trigger you should follow the best practice and create one Trigger per object.

A single Apex Trigger is all you need for one particular object. If you develop multiple Triggers for a single object, you have no way of controlling the order of execution if those Triggers can run in the same contexts.

Learn how to completely and cleanly control your Apex code with Trigger Frameworks, why they’re useful, and the various ways to implement them. Check this post to learn about Trigger Framework.

10. Use Asynchronous Apex

Apex written within an asynchronous method gets its own independent set of higher governor limits. For example, the number of SOQL queries doubles from 100 to 200 when using asynchronous calls. The total heap size and maximum CPU time are similarly larger for asynchronous calls.

Apex Best Practices. Demystifying Asynchronous Processing

11. Security and Sharing in Apex Code

One of the Apex Code best practice is developers must understand to code secure applications on the Salesforce Platform. Like how to enforce data security and how to prevent SOQL injection attacks in Apex.

Enforcing Object & FLS Permissions in Apex

Apex doesn’t enforce object-level and field-level permissions by default. Let see how we can enforce the CRUD & FLS in Apex.

  1. Schema methods
  2. WITH SECURITY_ENFORCED
  3. Security.stripInaccessible()
  4. Database operations in user mode (pilot)

Sharing Clauses for Apex Classes

Apex generally runs in system context, which means the current user’s permissions and field-level security take place during code execution. Our Apex code should not expose sensitive data to Users which is hidden via security and sharing settings. Hence, Apex security and enforcing the sharing rule is most important. Let’s see how we can implement the sharing in Apex

  1. with sharing
  2. without sharing
  3. inherited sharing

Check this post to learn more about Security in Apex.

12. Make reusability of Apex Code

The best code is written away from the keyboard. Break methods into multiple smaller methods if possible, making your code more modular and easier to read and maintain. A key best practice with Apex Code is to always write scalable and reusable code so that it can be reused in another functionality or module. This entails making code generic, so that it can be reused with minimum modification.

Learn more about Apex Enterprise Patterns here.

13. Code coverage

Unit tests are the test performed by the developers to ensure that functionality is working as expected considering Both positive & Negative tests. We should not focus on the percentage of code coverage. But it should follow the test class best practices.

  1. Bulk Records
  2. Positive & Negative testing.
  3. Restricted User testing.
  4. One Assert Statement per method
  5. should not use @isTest(seeAllData=true)
Apex Code coverage best practices

14. Return Early Pattern

Return early is the way of writing functions or methods so that the expected positive result is returned at the end of the function and the rest of the code terminates the execution

static void someAction(Case record)
    {
        if (/*condition 1*/){
            // do stuff
            return;
        }
        if (/*condition2*/)
        {
            // do stuff
            return;
        }
        // do stuff
        return;
    }

15. Avoid nesting loops within loops

Nested loops should be avoided in Apex controllers because they may slow down the processing of the page or may hit the governing limits for the page. One easy way, and which gives some nice structure to the code is to make the inner loop a separate function, or minimize using loops altogether.

Often we encounter code where we have two or three or more nested Loops which definitely affect performance. A simple way of avoiding nested loops is using Maps. For example, you can find or create a key for each item of the second loop and put the key and the value into the Map.

16. Don’t mix Apex, Process Builders, Workflow Rules, and Record-Triggered flows

Every object should have an automation strategy based on the needs of the business and the Salesforce team supporting it. In general, you should choose one automation tool per object. One of the many inevitable scenarios of older orgs is to have Apex triggers mixed in with Auto launched flows/processes or, more recently, Process Builders mixed in with Record-Triggered flows. This can lead to a variety of issues:

  1. Poor performance
  2. Unexpected results due to the inability to control the order of operations in the ‘stack’
  3. Increase in technical debt with admins/devs not collaborating
  4. Documentation debt

One common approach is to separate DML activity by Apex, and let declarative tools handle non-DML activities like email alerts and in-app alerts — just be careful and ensure none of your Apex conflicts.

17. Naming Conventions.

Another important best practice surrounding Apex Code is the need to follow a proper naming convention. Naming convention in Salesforce is a rule to follow as you decide what to name your identifiers like class, variable, constant, method, etc. But, it is not forced to follow. So, it is known as convention not rule. Naming conventions make the application easier to read and maintain. Check this post to learn more.

18. Setup Code review checklist and Code Review process

40%–80% of the lifetime cost of a piece of software goes to maintenance. To avoid future tech debt we should follow the code review process in each project and should create one code review checklist for developers.

No comments:

Post a Comment