This document outlines 10 commandments or best practices for Apex development on the Salesforce platform. The commandments are presented by two technical architects and include guidelines such as keeping code simple, avoiding queries and DML in loops, only using one trigger per object, focusing on test coverage, and writing meaningful tests. It is explained that these practices should generally be followed but are not strict rules, with the exception of bulkifying SOQL and DML queries. The document provides explanations for each recommendation to help developers write efficient, scalable and well-tested code.
15. #6 Thou shalt have a happy balance
between clicks & code
– “I’ve reached the maximum number of workflows”
– Triggers that replicate declarative functionality.
• Roll-up summary
• Workflows
• Flow Triggers
– Using Custom Settings instead of constructing a map in
Apex
– Using formulas instead of duplicating fields
Know which tool
is right for the
job.
17. #8 Thou shalt make use of relationships to reduce
queries wherever possible
18. #9 Thou shalt aim for 100% test coverage
In general test your methods for:
Positive effects.
• Given proper input it should act like this.
• Not just happy path, but all logic branches.
Negative effects.
• Given bad data it should error like this.
Role/Profile/User effects
Given a user with X profile and Y role it should act like this.
Bulkification
• Load 200 or more records to test limits.
19. #10 Thou shalt write meaningful and useful tests
• Not a test without System.assert calls
• System.assert(A==B,’A does not equal B’)
• System.AssertEquals(A,B)
• System.AssertNotEquals(A,B,’A equals B, but shouldnt’)
• The more assertions the better.
• Create mock data, never rely on existing production data.
• Test all conditions, not just positive test cases
• Test one criteria per unit test.
20.
21.
22.
23. #Bonus! Thou shalt feel free to break these as
wisdom merits.
After all:
Sometimes you just need a list of Id’s
These commandments, despite their
name, are guidelines for success, not
syntactical rules you just can’t break.
Break them if you need to, Adhere if you
can – they’ll save you time in the long run.
Except for
bulkfiying SOQL
and DML!
DEMO:
Showing developer console
Executing code and showing error
List<Account> accLst = new List<Account>([SELECT Id, Name FROM Account LIMIT 100]);
for (Account acc : accLst){
Integer numWithEmail = 0;
List<Contact> contactLst = new List<Contact>([SELECT Id, Email FROM Contact WHERE AccountId = :acc.Id]);
for (Contact cont : contactLst){
if (Cont.Email != null){
numWithEmail++;
}
}
System.debug(acc.Name+' has '+numWithEmail+' contacts with emails');
}
Make sure log levels are:
DB: Info
Callouts : Error
Apex Code: Debug
Validation: Error
Workflow: Error
Profilng: Debug
Visualforce: Error
System: Error
DEMO:
Change code in developer console.
Show debug log with now a query limit of 1.
List<Account> accLst = new List<Account>([SELECT
Id,
Name,
(SELECT Id, Email FROM Contacts)
FROM
Account
LIMIT 100]);
for (Account acc : accLst){
Integer numWithEmail = 0;
for (Contact cont : acc.Contacts){
if (Cont.Email != null){
numWithEmail++;
}
}
System.debug(acc.Name+' has '+numWithEmail+' contacts with emails');
}
DEMO:
Showing developer console
Executing code and showing error
List<Account> accLst = new List<Account>([SELECT Id, Name FROM Account LIMIT 100]);
for (Account acc : accLst){
Integer numWithEmail = 0;
List<Contact> contactLst = new List<Contact>([SELECT Id, Email FROM Contact WHERE AccountId = :acc.Id]);
for (Contact cont : contactLst){
if (Cont.Email != null){
numWithEmail++;
}
}
System.debug(acc.Name+' has '+numWithEmail+' contacts with emails');
}
Make sure log levels are:
DB: Info
Callouts : Error
Apex Code: Debug
Validation: Error
Workflow: Error
Profilng: Debug
Visualforce: Error
System: Error
DEMO:
Change code in developer console.
Show debug log with now a query limit of 1.
List<Account> accLst = new List<Account>([SELECT
Id,
Name,
(SELECT Id, Email FROM Contacts)
FROM
Account
LIMIT 100]);
for (Account acc : accLst){
Integer numWithEmail = 0;
for (Contact cont : acc.Contacts){
if (Cont.Email != null){
numWithEmail++;
}
}
System.debug(acc.Name+' has '+numWithEmail+' contacts with emails');
}
DEMO:
Showing developer console
Executing code and showing error
List<Account> accLst = new List<Account>([SELECT
Id,
Name,
Contacts_with_Email_Address__c,
(SELECT Id, Email FROM Contacts)
FROM
Account
LIMIT
151]);
for (Account acc : accLst){
Integer numWithEmail = 0;
for (Contact cont : acc.Contacts){
if (Cont.Email != null){
numWithEmail++;
}
}
System.debug(acc.Name+' has '+numWithEmail+' contacts with emails');
acc.Contacts_with_Email_Address__c = numWithEmail;
update acc;
}
List<Account> accLst = new List<Account>([SELECT
Id,
Name,
Contacts_with_Email_Address__c,
(SELECT Id, Email FROM Contacts)
FROM
Account
LIMIT
151]);
List<Account> accToUpd = new List<Account>();
for (Account acc : accLst){
Integer numWithEmail = 0;
for (Contact cont : acc.Contacts){
if (Cont.Email != null){
numWithEmail++;
}
}
System.debug(acc.Name+' has '+numWithEmail+' contacts with emails');
if (acc.Contacts_with_Email_Address__c <> numWithEmail){
acc.Contacts_with_Email_Address__c = numWithEmail;
accToUpd.add(acc);
}
}
update accToUpd;
List<Account> accLst = new List<Account>([SELECT
Id,
Name,
Contacts_with_Email_Address__c,
(SELECT Id, Email FROM Contacts)
FROM
Account
LIMIT
151]);
List<Account> accToUpd = new List<Account>();
for (Account acc : accLst){
Integer numWithEmail = 0;
for (Contact cont : acc.Contacts){
if (Cont.Email != null){
numWithEmail++;
}
}
System.debug(acc.Name+' has '+numWithEmail+' contacts with emails');
if (acc.Contacts_with_Email_Address__c <> numWithEmail){
acc.Contacts_with_Email_Address__c = numWithEmail;
accToUpd.add(acc);
}
}
update accToUpd;
Declarative crazy. 500+ fields on an object, loads of workflows – It can get really hard to see what’s going on in the same way bad coding can be hard.
When querying for a collection of records, you can query into either a list or a map. By default it seems we all learn to query into a list, like we see there in the first block of code.
In complex logic situations the ability to pull from the collection a specific record, by it’s id is essential.
In refactoring situations it’s easy to convert your list to a map (line 10)
Queries are limited.
Use your queries wisely, and remember to query UP.
Start with the childmost object and reference parent objects through the relationship __r fields
Positive Test cases should check for expected results not only of the unit overall but of logic branches.