Apex Best Practices: Do’s and Don’ts

Share This Post

Writing efficient, scalable, and maintainable Apex code is critical to the long-term success of your Salesforce org. Below are essential Apex best practices, explained with “Do this ✅” and “Don’t do this ❌” examples.


1. 🧠 Bulkify Your Code

❌ Don’t: Process records one at a time.

public class AccountHelper {
    public static void updateNames(List<Id> ids) {
        for (Id id : ids) {
            Account acc = [SELECT Id, Name FROM Account WHERE Id = :id];
            acc.Name += ' - Updated';
            update acc;
        }
    }
}

✅ Do: Query and update records in bulk.

public class AccountHelper {
    public static void updateNames(List<Id> ids) {
        List<Account> accList = [SELECT Id, Name FROM Account WHERE Id IN :ids];
        for (Account acc : accList) {
            acc.Name += ' - Updated';
        }
        update accList;
    }
}

2. 🔁 Avoid DML or SOQL in Loops

❌ Don’t:

for (Contact c : contacts) {
    insert c;
}

✅ Do:

insert contacts;

3. 📦 Use Maps for Efficient Lookups

❌ Don’t: Re-query inside loops.

for (Opportunity opp : oppList) {
    Account acc = [SELECT Name FROM Account WHERE Id = :opp.AccountId];
    opp.Description = acc.Name;
}

✅ Do: Use Maps to avoid redundant queries.

Set<Id> accIds = new Set<Id>();
for (Opportunity opp : oppList) {
    accIds.add(opp.AccountId);
}

Map<Id, Account> accMap = new Map<Id, Account>(
    [SELECT Id, Name FROM Account WHERE Id IN :accIds]
);

for (Opportunity opp : oppList) {
    opp.Description = accMap.get(opp.AccountId).Name;
}

4. 📂 One Trigger Per Object

❌ Don’t: Create multiple triggers on the same object.
🛑 This causes unpredictable execution order.

✅ Do: Use a single trigger and delegate logic to a handler class.

trigger AccountTrigger on Account (before insert, before update) {
    AccountTriggerHandler.handle(Trigger.new, Trigger.isInsert);
}

5. 🧰 Use Trigger Handlers

❌ Don’t: Put logic directly in the trigger.

trigger ContactTrigger on Contact (before insert) {
    for (Contact con : Trigger.new) {
        con.Description = 'New Contact';
    }
}

✅ Do: Use a separate handler class.

trigger ContactTrigger on Contact (before insert) {
    ContactTriggerHandler.beforeInsert(Trigger.new);
}

public class ContactTriggerHandler {
    public static void beforeInsert(List<Contact> newContacts) {
        for (Contact con : newContacts) {
            con.Description = 'New Contact';
        }
    }
}

6. 🧪 Write Effective Test Classes

❌ Don’t: Write tests without asserts.

@isTest
static void testInsert() {
    Account acc = new Account(Name='Test');
    insert acc;
}

✅ Do: Validate actual outcomes with assertions.

@isTest
static void testInsert() {
    Account acc = new Account(Name='Test');
    insert acc;

    Account inserted = [SELECT Name FROM Account WHERE Id = :acc.Id];
    System.assertEquals('Test', inserted.Name);
}

7. 🔐 Avoid Hardcoding IDs

❌ Don’t:

if (UserInfo.getProfileId() == '00e1x000000abcD') {
    // logic
}

✅ Do:

Profile p = [SELECT Id FROM Profile WHERE Name = 'System Administrator' LIMIT 1];
if (UserInfo.getProfileId() == p.Id) {
    // logic
}

8. ⏳ Use Asynchronous Apex for Long-Running Jobs

❌ Don’t: Do heavy operations synchronously.

public void sendEmails() {
    // Loops with large DML or callouts
}

✅ Do: Use @future, Queueable, or Batch Apex.

@future
public static void sendEmailsAsync() {
    // Perform callout/DML
}

9. ⚠️ Handle Exceptions Gracefully

❌ Don’t:

update accList; // Might throw DmlException

✅ Do:

try {
    update accList;
} catch (DmlException e) {
    System.debug('Error: ' + e.getMessage());
}

10. 🔂 Prevent Trigger Recursion

❌ Don’t: Let logic run endlessly due to updates within triggers.

✅ Do:

public class RecursionControl {
    public static Boolean hasRun = false;
}
if (!RecursionControl.hasRun) {
    RecursionControl.hasRun = true;
    // update logic
}

11. 🧼 Avoid Nested Loops for Performance

❌ Don’t:

for (Account acc : accounts) {
    for (Contact con : contacts) {
        if (con.AccountId == acc.Id) { ... }
    }
}

✅ Do:

Map<Id, List<Contact>> accountContacts = new Map<Id, List<Contact>>();
for (Contact con : contacts) {
    if (!accountContacts.containsKey(con.AccountId)) {
        accountContacts.put(con.AccountId, new List<Contact>());
    }
    accountContacts.get(con.AccountId).add(con);
}

12. 🛡️ Defensive Programming

❌ Don’t: Assume data always exists.

update accounts;

✅ Do:

if (!accounts.isEmpty()) {
    update accounts;
}

13. 🧾 Follow Naming Conventions & Document Code

❌ Don’t: Use vague variable names.

List<Account> a1 = new List<Account>();

✅ Do:

List<Account> customerAccounts = new List<Account>();

✅ Add Comments:

// Set default value before insert
acc.Status__c = 'New';

Final Word 💬

These best practices ensure:

  • Scalability and performance
  • Readability and maintainability
  • Fewer bugs and better testability

2 Responses

Leave a Reply to Aman Garg Cancel reply

Your email address will not be published. Required fields are marked *

Subscribe To Our Newsletter

Get updates and learn from the best

More To Explore