10 Best Practices for Apex Code

Standardized coding conventions in the software engineering community help keep code relevant and useful for clients, future developers, and coders themselves. Any professional programmer will tell you that they spend more time reading code than writing it. Working on a code base is more likely than writing something from scratch. As a result, it’s critical that your code is simple enough for other developers to understand.

In this article, we’ll look at some of Salesforce Apex’s coding conventions and best practices.

1. Avoid DML/SOQL/SOSL statements in loops

Avoid using DML statements inside loops to avoid exceeding the DML governor limit. Instead, try batching the data into a list and invoking your DML once on that list of data outside the loop.

Database class methods, DML operations, SOQL queries, SOSL queries, Approval class methods, Email sending, async scheduling, or queueing within loops can cause governor limit exceptions. Instead, try to batch up the data into a list and invoke the operation once on that list of data outside the loop.

SOQL/SOSL calls within loops can cause governor limit exceptions. Instead of using SOQL/SOSL queries in the loop, use collections such as Map, List, and Set.

Bad example: DML statement written in the for a loop. The governor limit will be exceeded if there are more than150 account records to update.

The example code below demonstrates how to write DML outside of a for loop.

2. Avoid hardcoding ID

When deploying Apex code between sandbox and production environments or installing Force.com AppExchange packages, it is crucial to avoid hardcoding IDs in the Apex code. As a result, if the record IDs change between environments, the logic can dynamically identify the correct data to operate against and not fail.

// KO
public static void foo() { 

        if (opp.RecordTypeId == '016500000005WAr') {
            //do some logic here.....
        } else if (opp.RecordTypeId == '016500000008WAr') {
            //do some logic here for a different record type...
        }
    }

If you run the above code in different environments, it will fail. The code snippet below is the best way to implement this type of requirement.

// OK
public static void foo() {

        if (opp.RecordTypeId ==           Schema.sObjectType.Opportunity.getRecordTypeInfosByName().get('Local') ) {
            //do some logic here.....
        } else if (opp.RecordTypeId == Schema.sObjectType.Opportunity.getRecordTypeInfosByName().get('Regional')) {
            //do some logic here for a different record type...
        }
}

3. Use only one trigger per SObject Type.

Multiple triggers on the same object are not recommended because Salesforce cannot guarantee the order of execution, so we should only add one trigger per object.

One trigger per object template

Benefits of using one trigger per object template

  • Control order of execution: This pattern allows you to control the order of execution.
  • Reusability: When you write your logic in apex class then it can be reused outside of trigger as well. For example in the lightning component controller, test class, batch class, visualforce page controller, etc.
  • Elegance: This design pattern elevates the code’s elegance, organization, and modularity.

4. Avoid logic in the trigger

Triggers are less flexible and suited to applying a good encapsulation style because they do not allow methods like regular classes. Therefore delegate the triggers to a regular class (often called Trigger handler/helper class).

To avoid putting your logic in a trigger, use a single trigger per object template and write a handler and helper class. See the screenshots below for an AccounTrigger example.

This is how the TriggerHandler interface will look.

AccountTriggerHandler

In the AccountTriggerHelper class, write your actual logic.

5. Bulkify your Apex code logic

You should write bulkified code so that it can handle multiple records at the same time and process it efficiently.

Take a look at the code below – this is a bad example of code that has not been bulkified. Salesforce Data Loader tool is used to upload the bulk records. This code can only process a single record in a chunk of 200 records.

The code below has been bulkified so that it can process your logic for all records.

6. Apex SOQL injection

Detects the use of untrusted / unescaped variables in DML queries.

ex. SELECT Id FROM Contact WHERE (IsDeleted = false AND Name LIKE '%test%') OR (Name LIKE '%')

the results show all contacts, not just the non-deleted ones. A SOQL Injection flaw can be used to modify the intended logic of any vulnerable query.

To prevent a SOQL injection attack, avoid using dynamic SOQL queries. Instead, use static queries and binding variables. The vulnerable example above can be re-written using static SOQL as follows:

public class SOQLController {
    public String name {
        get { return name;}
        set { name = value;}
	}
    public PageReference query() {
        String queryName = '%' + name + '%';
        queryResult = [SELECT Id FROM Contact WHERE
           (IsDeleted = false and Name like :queryName)];
        return null;
	}
}

If you must use dynamic SOQL, use the escapeSingleQuotes method to sanitize user-supplied input. This method adds the escape character () to all single quotation marks in a string that is passed in from a user. The method ensures that all single quotation marks are treated as enclosing strings, instead of database commands.

7. Apex unit test should not use SeeAllData True

Apex unit tests should not use @isTest(seeAllData=true) because it exposes existing database data to unexpected changes by tests.

By annotating your class with @isTest(SeeAllData=true), you enable test methods to access all org records. The best practice, however, is to run Apex tests with data silos using @isTest(SeeAllData=false).

8. For loops must use braces

Avoid using ‘for’ statements without surrounding braces. If the code formatting or indentation is lost, it becomes difficult to distinguish the code being controlled from the rest.

for (integer i = 0; i < 42; i++) // not recommended
    myMethod();
for (integer i = 0; i < 42; i++) { // preferred approach
    myMethod();
}

9. Naming conventions

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 but the Apex PMD naming convention is generally used.

Class naming conventions

Standard Apex naming convention (Pascal case)

public class FooClass { } // This is in pascal case, so it's ok
public class fooClass { } // KO, this will be reported an an issue in apex PMD code analysis

Method naming conventions

Camel case

public class Foo {
    public void instanceMethod() { } // This is in camel case, so it's ok
 
    public void INSTANCE_METHOD() { } // NOT OK
 }

Field or local variable naming conventions

Camel case

public class Foo {
    Integer instanceField; // This is in camel case, so it's ok
    Integer INSTANCE_FIELD; // NOT OK
}

10. Avoid Debug Statements

Debug statements contribute to longer transactions and consume Apex CPU time even when debug logs are not captured.

When possible, use other debugging techniques, such as the Apex Replay Debugger and Checkpoints, which can cover the majority of use cases.

Bonus – Apex unit test class should have asserts

A minimum of one assertion should be included in Apex unit tests. This strengthens the tests, and using assert with messages helps the developer understand what the test does.

  • One Assert Statement per method: Always include assert statements for both positive and negative tests.
  • System.assert(condition, msg)
  • System.assertEquals(expected, actual, msg)
  • System.assertNotEquals(expected, actual, msg)

Best practices will not only help you code more efficiently but will also raise awareness of responsible coding in the field of software development.

Apex Trigger Template

Salesforce Apex Code Quality Rules

Join 736 other followers

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s