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

Subscribe To Our Newsletter

Get updates and learn from the best

More To Explore

Discover more from SF Learners Hub

Subscribe now to keep reading and get access to the full archive.

Continue reading