Salesforce Apex Code Best Practices

Salesforce Apex Code Best Practices

On September 3, 2024, Posted by , In Salesforce Apex Tutorial, With Comments Off on Salesforce Apex Code Best Practices
Salesforce Apex Code Best Practices
Salesforce Apex Code Best Practices

Table of Contents

When developing on the Salesforce platform, adhering to Apex best practices is crucial for ensuring efficient, maintainable, and scalable code. Salesforce Apex best practices such as bulkifying Apex code, avoiding SOQL and DML inside loops, and optimizing queries are essential for performance. Following Apex best practices like using the Limits Apex methods, proper exception handling, and adhering to naming conventions helps maintain high code quality. Enforcing security and sharing rules in your Apex code is another critical best practice to protect data integrity. Overall, implementing these Salesforce Apex best practices leads to robust and reliable applications.

1. Bulkify Apex Code

Bulkifying Apex code is a practice that ensures your code can handle large data volumes efficiently. This means making sure your code processes records in bulk rather than one at a time, which helps in optimizing performance and avoiding governor limits.

Code Snippet Example:

// Incorrect: Processing records one by one
for (Account acc : Trigger.New) {
    Contact newContact = new Contact(LastName = 'Default', AccountId = acc.Id);
    insert newContact;
}

// Correct: Bulk processing
List<Contact> newContacts = new List<Contact>();
for (Account acc : Trigger.New) {
    newContacts.add(new Contact(LastName = 'Default', AccountId = acc.Id));
}
insert newContacts;

The first example inserts contacts inside a loop, which can quickly hit governor limits. The second example collects all new contacts in a list and performs a single DML operation, making it more efficient.

Preparing for your next Salesforce interview? Check out these essential Salesforce interview questions and answers to give you an edge.

2. Avoid SOQL & DML Inside For Loop

Placing SOQL (Salesforce Object Query Language) and DML (Data Manipulation Language) operations inside loops can lead to hitting governor limits due to the large number of queries and operations executed. This practice ensures your code is scalable and efficient.

Code Snippet Example:

// Incorrect: SOQL inside for loop
for (Account acc : Trigger.New) {
    List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}

// Correct: SOQL outside for loop
Set<Id> accountIds = new Set<Id>();
for (Account acc : Trigger.New) {
    accountIds.add(acc.Id);
}
List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];

The first example runs a query inside the loop, potentially leading to governor limit issues. The second example gathers all account IDs and performs a single query outside the loop, improving efficiency.

3. Optimize SOQL Queries to avoid Timeout Issues

Introduction: Optimizing SOQL queries involves writing efficient queries that return the necessary data quickly, without causing timeout issues. This includes using selective filters, indexing fields, and minimizing the number of returned rows and fields.

Code Snippet Example:

Explanation: The first example fetches all accounts and their related contacts, which can be slow and resource-intensive. The optimized query applies a filter and limit to return only the necessary data, reducing the likelihood of timeouts.

4. Use Of Map Of Sobject

Using a Map of SObject is an efficient way to handle related data. It allows for quick lookups and efficient processing by leveraging key-value pairs, where the key is typically an ID and the value is the SObject.

Code Snippet Example:

// Using List
List<Account> accounts = [SELECT Id, Name FROM Account];
for (Account acc : accounts) {
    // Process account
}

// Using Map
Map<Id, Account> accountMap = new Map<Id, Account>([SELECT Id, Name FROM Account]);
for (Id accId : accountMap.keySet()) {
    Account acc = accountMap.get(accId);
    // Process account
}

The map example allows for quicker access to accounts by their IDs compared to iterating through a list. This is particularly useful when you need to frequently access or update records by their IDs.

5. Use Of The Limits Apex Methods

The Limits Apex Methods provide a way to monitor governor limits during code execution. Using these methods helps developers write efficient code that stays within Salesforce’s governor limits.

Code Snippet Example:

// Checking SOQL limits
Integer soqlQueriesBefore = Limits.getQueries();
List<Account> accounts = [SELECT Id, Name FROM Account];
Integer soqlQueriesAfter = Limits.getQueries();
System.debug('Number of SOQL queries used: ' + (soqlQueriesAfter - soqlQueriesBefore));

// Checking DML limits
Integer dmlStatementsBefore = Limits.getDmlStatements();
update accounts;
Integer dmlStatementsAfter = Limits.getDmlStatements();
System.debug('Number of DML statements used: ' + (dmlStatementsAfter - dmlStatementsBefore));

This example demonstrates how to use the Limits methods to monitor SOQL queries and DML statements. By checking the number of queries and statements before and after an operation, you can ensure your code remains within acceptable limits.

Master the Database methods in Salesforce Apex to perform sophisticated database operations with ease.

6. Avoid Hardcoding IDs

Hardcoding IDs in your Apex code is a bad practice because it makes the code less flexible and more difficult to maintain. Instead, use custom settings, custom metadata types, or queries to retrieve the necessary IDs dynamically.

Code Snippet Example:

// Incorrect: Hardcoding ID
Id recordTypeId = '0123456789abcdef';

// Correct: Using a dynamic query
Id recordTypeId = [SELECT Id FROM RecordType WHERE SObjectType = 'Account' AND Name = 'Business Account' LIMIT 1].Id;

The first example hardcodes an ID, which can change between environments, leading to errors. The second example dynamically retrieves the ID, making the code more robust and portable.

7. Use Database Methods While Doing DML Operation

Using Database class methods (like Database.insert, Database.update, etc.) is preferred for DML operations because they allow for partial success and error handling. This provides more control over how DML operations are executed.

Code Snippet Example:

// Using standard DML
try {
    insert newAccount;
} catch (DmlException e) {
    System.debug('Error: ' + e.getMessage());
}

// Using Database methods
Database.SaveResult sr = Database.insert(newAccount, false);
if (!sr.isSuccess()) {
    for (Database.Error err : sr.getErrors()) {
        System.debug('Error: ' + err.getMessage());
    }
}

The standard DML operation throws an exception on error, which can be caught and handled. The Database method returns a SaveResult object, which can be used to check for errors and handle them more gracefully.

Readmore: Validation Rules in Salesforce

8. Exception Handling In Apex Code

Proper exception handling in Apex code ensures that errors are managed gracefully without disrupting the user experience. It involves using try-catch blocks to handle exceptions and take appropriate action when an error occurs.

Code Snippet Example:

// Example with try-catch block
try {
    Account newAccount = new Account(Name = 'Test Account');
    insert newAccount;
} catch (DmlException e) {
    System.debug('Error: ' + e.getMessage());
    // Additional error handling logic
}

This example shows a try-catch block used to handle a DmlException during an insert operation. Proper error handling ensures that any issues are logged and managed without causing the entire transaction to fail.

9. Write One Trigger Per Object Per Event

Writing one trigger per object per event ensures that your triggers are easier to manage and debug. It also helps in maintaining a clear separation of logic and prevents conflicts that can arise from multiple triggers on the same object.

Code Snippet Example:

// Trigger for Account before insert
trigger AccountBeforeInsert on Account (before insert) {
    for (Account acc : Trigger.new) {
        acc.Description = 'This account was created by a trigger';
    }
}

// Separate trigger for Account after insert
trigger AccountAfterInsert on Account (after insert) {
    List<Contact> newContacts = new List<Contact>();
    for (Account acc : Trigger.new) {
        newContacts.add(new Contact(LastName = 'Default', AccountId = acc.Id));
    }
    insert newContacts;
}

This example shows two separate triggers for the Account object: one for the before insert event and another for the after insert event. This separation makes the triggers more manageable and easier to maintain.

10. Use Asynchronous Apex

Asynchronous Apex, such as @future methods, batch Apex, and Queueable Apex, allows you to run processes in the background. This is useful for operations that require more processing time or need to be run at specific intervals.

Code Snippet Example:

// Example of @future method
@future
public static void processLargeData(List<Id> recordIds) {
    List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :recordIds];
    for (Account acc : accounts) {
        acc.Description = 'Processed in future method';
    }
    update accounts;
}

This example uses an @future method to process a large set of account records in the background. This approach prevents long-running operations from blocking the main transaction and ensures better performance and user experience.

11. Security And Sharing In Apex Code

Security and sharing in Apex code are essential to ensure that your code respects the organization’s data access policies. This involves using methods that enforce sharing rules and field-level security.

// Using without sharing keyword
public without sharing class AccountService {
    public List<Account> getAccounts() {
        return [SELECT Id, Name FROM Account];
    }
}

// Using with sharing keyword
public with sharing class AccountService {
    public List<Account> getAccounts() {
        return [SELECT Id, Name FROM Account];
    }
}

The first example ignores the sharing rules by using the without sharing keyword, which can expose sensitive data. The second example respects the sharing rules using the with sharing keyword, ensuring that only the data the user is allowed to see is retrieved.

Read more: Classes in Salesforce Apex

12. Make Reusability Of Apex Code

Reusability of Apex code means writing code that can be reused across different parts of your application, reducing redundancy and improving maintainability. This can be achieved by creating utility classes and methods.

// Reusable utility method
public class AccountUtils {
    public static void updateAccountDescription(List<Id> accountIds, String description) {
        List<Account> accounts = [SELECT Id FROM Account WHERE Id IN :accountIds];
        for (Account acc : accounts) {
            acc.Description = description;
        }
        update accounts;
    }
}

// Using the utility method
List<Id> accIds = new List<Id>{'0010K00002q6QqQ', '0010K00002q6QqR'};
AccountUtils.updateAccountDescription(accIds, 'Updated via utility method');

By creating the AccountUtils class with a static method to update account descriptions, the code can be reused in multiple places without duplicating the logic.

13. Code Coverage

Code coverage in Salesforce refers to the measure of how much of your Apex code is exercised by unit tests. Salesforce requires at least 75% code coverage for deployment to production, ensuring that your code is well-tested.

// Apex class
public class AccountHandler {
    public void updateAccountName(Id accountId, String newName) {
        Account acc = [SELECT Id, Name FROM Account WHERE Id = :accountId];
        acc.Name = newName;
        update acc;
    }
}

// Test class
@isTest
private class AccountHandlerTest {
    @isTest
    static void testUpdateAccountName() {
        Account testAccount = new Account(Name = 'Test Account');
        insert testAccount;

        AccountHandler handler = new AccountHandler();
        handler.updateAccountName(testAccount.Id, 'Updated Account');

        Account updatedAccount = [SELECT Name FROM Account WHERE Id = :testAccount.Id];
        System.assertEquals('Updated Account', updatedAccount.Name);
    }
}

This example includes both an Apex class and a test class, ensuring that the updateAccountName method is covered by the test, thereby contributing to overall code coverage.

Read more: SOQL Query in Salesforce

14. Return Early Pattern

The return early pattern involves exiting a method as soon as a condition is met, improving readability and reducing the complexity of your code. It helps in avoiding nested conditional statements.

public class OpportunityService {
    public void closeOpportunity(Id opportunityId) {
        Opportunity opp = [SELECT Id, StageName FROM Opportunity WHERE Id = :opportunityId];
        if (opp == null) {
            return;
        }

        if (opp.StageName == 'Closed Won') {
            return;
        }

        opp.StageName = 'Closed Won';
        update opp;
    }
}

In this example, the method returns early if the opportunity is not found or if it is already closed, simplifying the logic and making the code easier to understand and maintain.

15. Avoid Nesting Loops Within Loops

Nesting loops within loops can lead to performance issues, especially with large datasets. Instead, use collections and maps to handle data more efficiently.

// Inefficient nested loops
for (Account acc : [SELECT Id, Name FROM Account]) {
    for (Contact con : [SELECT Id, AccountId FROM Contact WHERE AccountId = :acc.Id]) {
        // Process each contact
    }
}

// Efficient use of maps
List<Account> accounts = [SELECT Id, Name FROM Account];
Set<Id> accountIds = new Set<Id>();
for (Account acc : accounts) {
    accountIds.add(acc.Id);
}

Map<Id, List<Contact>> accountContactsMap = new Map<Id, List<Contact>>();
for (Contact con : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
    if (!accountContactsMap.containsKey(con.AccountId)) {
        accountContactsMap.put(con.AccountId, new List<Contact>());
    }
    accountContactsMap.get(con.AccountId).add(con);
}

for (Account acc : accounts) {
    List<Contact> contacts = accountContactsMap.get(acc.Id);
    // Process contacts for each account
}

By using maps to associate accounts with their contacts, the example avoids nested loops and improves performance, especially when dealing with large datasets.

16. Don’t Mix Apex, Process Builders, Workflow Rules, And Record-Triggered Flows

Mixing Apex, Process Builders, Workflow Rules, and Record-Triggered Flows can lead to unpredictable behavior and difficult-to-debug issues due to overlapping logic. It’s best to choose one automation tool per object and stick with it for consistency and maintainability.

// Apex Trigger
trigger AccountTrigger on Account (before insert, before update) {
    for (Account acc : Trigger.new) {
        if (acc.Industry == 'Technology') {
            acc.Description = 'Tech Industry';
        }
    }
}

// Avoid combining with Process Builder for the same logic

If you already have an Apex trigger handling logic for an object, avoid creating Process Builders or Workflow Rules that perform similar or conflicting actions. Stick to one tool to ensure the logic is centralized and easier to manage.

Read more: Loops in Salesforce Apex

17. Apex Naming Conventions

Following consistent naming conventions in Apex code improves readability and maintainability. This includes naming classes, methods, variables, and triggers in a clear and descriptive manner.

// Example of good naming conventions
public class AccountService {
    public void updateAccountName(Id accountId, String newName) {
        Account account = [SELECT Id, Name FROM Account WHERE Id = :accountId];
        account.Name = newName;
        update account;
    }
}

// Trigger naming convention
trigger AccountBeforeInsert on Account (before insert) {
    for (Account acc : Trigger.new) {
        acc.Description = 'Account created';
    }
}

In this example, the class name AccountService, method name updateAccountName, and trigger name AccountBeforeInsert clearly describe their purpose, making the code easier to understand and maintain.

18. Setup Code Review Checklist And Code Review Process

Setting up a code review checklist and process ensures that all code is reviewed for quality, performance, and adherence to best practices before being merged into the main codebase. This helps catch errors early and promotes consistency.

// Example code review checklist
1. Ensure bulkification of Apex code.
2. Avoid SOQL and DML operations inside loops.
3. Use meaningful naming conventions.
4. Verify code coverage with sufficient unit tests.
5. Check for proper exception handling.
6. Ensure security and sharing rules are enforced.
7. Validate that no hardcoded IDs are used.
8. Confirm adherence to the return early pattern.
9. Avoid deeply nested loops.
10. Review for potential performance bottlenecks.

Implementing a checklist like this helps reviewers ensure that all critical aspects of the code are checked, leading to higher quality and more maintainable code.

19. Apex Performance

Apex performance optimization involves writing efficient code that minimizes resource usage and executes quickly. This includes using efficient data structures, optimizing queries, and avoiding unnecessary computations.

// Inefficient Apex code
List<Account> accounts = [SELECT Id, Name FROM Account];
for (Account acc : accounts) {
    acc.Description = 'Updated';
    update acc;
}

// Optimized Apex code
List<Account> accounts = [SELECT Id, Name FROM Account];
for (Account acc : accounts) {
    acc.Description = 'Updated';
}
update accounts;

In the optimized example, the update operation is performed in bulk outside the loop, reducing the number of DML operations and improving performance.

20. Use Custom Settings For Configurable Data

Using custom settings for configurable data allows administrators to manage settings and preferences without changing the code. This is useful for managing feature toggles, configuration values, and other settings that might change over time.

// Custom setting definition
CustomSetting__c setting = CustomSetting__c.getValues('SettingName');
if (setting != null && setting.Enable_Feature__c) {
    // Feature-specific logic
}

// Example custom setting
public class FeatureToggle {
    public static Boolean isFeatureEnabled() {
        CustomSetting__c setting = CustomSetting__c.getValues('SettingName');
        return setting != null && setting.Enable_Feature__c;
    }
}

By using custom settings, you can easily enable or disable features and update configuration values without modifying and deploying Apex code. This approach improves flexibility and reduces the need for frequent code changes.

Why Should You Learn Salesforce?

Salesforce has become a global leader in the tech world, renowned for its industry-dominating cloud-based CRM solutions. Major companies such as Google, Amazon, and Facebook rely heavily on Salesforce to manage customer interactions, streamline operations, and drive business growth. By learning Salesforce, you can unlock a variety of career paths, including Salesforce Admin, Developer, Business Analyst, and Architect roles. Whether you’re new to tech or seeking to advance your existing career, Salesforce offers tremendous growth potential. In terms of salaries, Salesforce professionals in India typically earn between INR 5 to 20 lakhs annually, while those in the USA can earn between $80,000 and $150,000 per year, depending on experience and role.

At CRS Info Solutions, we provide a comprehensive Salesforce training program in Pune, crafted to help aspirants, from beginners to professionals, learn Salesforce efficiently. Our curriculum is the most extensive in the market, covering everything you need to know, including updated interview questions and full certification exam preparation at a highly affordable price. We offer daily notes after each class to reinforce key concepts and recorded video sessions so you can revisit any topic at your convenience. Our practical, hands-on training approach includes mock interviews, ensuring you are job-ready by the end of the course. With over 12,000+ students successfully trained, our experienced instructors are dedicated to helping you build the confidence and skills needed to land your dream Salesforce job.

Sign up for our free demo orientation today and take the first step towards a successful career in Salesforce with CRS Info Solutions, where you’ll receive top-notch mentorship and hands-on training to excel in the industry!

Comments are closed.