
Mastering Salesforce Development:
Whether you’re a seasoned Salesforce developer or an admin just getting started, you’ve probably come across the term “best practice.” But what exactly does it mean?
Simply put, a best practice is a widely accepted technique or approach that consistently yields better results than alternatives. It’s the gold standard—a reliable way to write scalable, maintainable, and efficient code on the Salesforce platform. In this guide, we’ll explore essential best practices for Salesforce Apex, explain why they matter, and even discuss scenarios where exceptions may apply.
1. Bulkify Your Code
What is it?
Bulkification means designing your code to handle multiple records simultaneously. In Salesforce, triggers can process up to 200 records in a single transaction (e.g., during a data import). If your code doesn’t account for this, errors, slow performance, or unintended behaviors can occur.
Example of Non-Bulkified Code:
- trigger OpportunityTrigger on Opportunity (before insert) {
- for (Opportunity opp : Trigger.new) {
- opp.StageName = ‘Prospecting’;
- insert opp; // DML inside loop – Bad practice
- }
- }
Bulkified Version:
trigger OpportunityTrigger on Opportunity (before insert) {
for (Opportunity opp : Trigger.new) {
opp.StageName = ‘Prospecting’;
}
insert Trigger.new; // Single DML operation outside the loop
}
Why it matters:
Bulkification not only ensures your code adheres to Salesforce governor limits but also boosts performance and scalability.
2. Avoid SOQL/DML Statements in Loops
What is it?
SOQL (Salesforce Object Query Language) and DML (Data Manipulation Language) operations are expensive and governed by strict limits. Running these inside loops can quickly cause your transaction to exceed limits.
What to do instead:
• Use collections (lists, maps, or sets) to gather records in the loop.
• Perform a single SOQL query or DML operation outside the loop.
Example: Avoid DML in a Loop
// Bad: DML in loop
for (Contact con : contacts) {
insert con;
}
// Good: DML outside loop
insert contacts;
Example: Avoid SOQL in a Loop
// Bad: SOQL in loop
for (Contact con : contacts) {
Account acc = [SELECT Name FROM Account WHERE Id = :con.AccountId];
}
// Good: Query once, use a map
Set<Id> accountIds = new Set<Id>();
for (Contact con : contacts) {
accountIds.add(con.AccountId);
}
Map<Id, Account> accountsMap = new Map<Id, Account>([SELECT Id, Name FROM Account WHERE Id IN :accountIds]);
3. Avoid Hard-Coded IDs
Hardcoding record IDs (e.g., for record types or specific records) leads to issues when migrating code between environments. IDs vary across orgs, so relying on them will break your code.
Better Alternatives:
1. Use DeveloperName for Record Types:
Id recordTypeId = Schema.SObjectType.Contact.getRecordTypeInfosByDeveloperName().get(‘Customer’).getRecordTypeId();
2. Store IDs in Custom Metadata:
Custom Metadata Types allow you to manage IDs and make them environment-agnostic.
4. Explicitly Declare Sharing Models
Always declare your sharing model (with sharing, without sharing, or inherited sharing) when writing classes. This ensures transparency in how your code respects Salesforce security settings.
Example:
public with sharing class MyClass {
// Enforces sharing rules
}
When to use without sharing:
If the class needs to bypass sharing rules (e.g., for admin-level operations).
Pro tip: Use inherited sharing for utility classes to let the caller dictate the sharing context.
5. Use a Single Trigger per SObject
Why?
When multiple triggers exist on the same object, Salesforce doesn’t guarantee their execution order. This can cause unpredictable behavior and make debugging a nightmare.
Solution:
Use a trigger handler pattern to centralize and manage logic for each object.
trigger AccountTrigger on Account (before update, after insert) {
AccountTriggerHandler.handleTrigger(Trigger.new, Trigger.old);
}
6. Leverage SOQL for Loops
When querying large datasets, avoid storing all records in memory to prevent heap size issues. Instead, use SOQL within the loop for efficient chunking.
Example:
for (Account acc : [SELECT Id, Name FROM Account WHERE Industry = ‘Technology’]) {
System.debug(acc.Name);
}
7. Modularize Your Code
Avoid copying and pasting the same logic across classes. Instead, abstract reusable logic into utility classes or methods.
Example:
Instead of embedding dynamic SOQL logic in multiple places:
public class SOQLHelper {
public static List<Account> getAccountsByIndustry(String industry) {
return [SELECT Id, Name FROM Account WHERE Industry = :industry];
}
}
8. Write Comprehensive Test Classes
Aim for quality over quantity in your test cases. Instead of focusing solely on reaching 75% code coverage, test a variety of scenarios to ensure functionality.
Example Scenarios to Test:
• Positive and negative cases
• Bulk record processing
• Different user contexts (with/without access)
Validate Expected Results:
System.assertEquals(‘Expected Value’, actualValue);
9. Avoid Nested Loops
Nested loops are not only harder to read but can also lead to governor limit issues. Instead, use data structures like maps to avoid them.
Example:
// Avoid this:
for (Account acc : accounts) {
for (Contact con : acc.Contacts) {
// Nested loop logic
}
}
Better Alternative:
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
// Process contacts by accountId outside nested loops
10. Follow Consistent Naming Conventions
Adopt clear, consistent naming conventions for variables, methods, and classes to make your code readable and maintainable.
Example:
• Class: OpportunityTriggerHandler
• Method: calculateDiscount()
• Variable: totalAmount
11. Keep Business Logic Out of Triggers
Triggers should only delegate to handler classes; they shouldn’t contain business logic directly.
Example Trigger Structure:
trigger AccountTrigger on Account (before insert) {
AccountHandler.handleBeforeInsert(Trigger.new);
}
12. Avoid Returning JSON to Lightning Components
When returning data from @AuraEnabled methods, let Salesforce handle serialization instead of manually converting objects to JSON. This avoids consuming unnecessary heap space and CPU cycles.
Example:
@AuraEnabled
public static List<Contact> getContacts() {
return [SELECT Id, Name FROM Contact];
}
Conclusion
By adopting these best practices, you’ll not only build better Salesforce solutions but also save yourself (and your team) time and headaches in the future. Whether you’re coding triggers, writing tests, or managing data, these techniques will set you up for long-term success.
Are you following these best practices in your Salesforce development? Share your thoughts and tips below!


Leave a comment