Salesforce - Developer - Best Practices
// Salesforce - Apex - Best Practices:
1. Thy shall not put SOQL inside loops.
2. Thy shall not put DML (insert, update, etc) inside loops. Do bulk insert,
or create sub-select statements.
3. Thou shall have a happy balance between clicks and code.
Triggers that replicate declarative functionality:
a. Roll-up summary
b. Workflows
c. Email Templates
d. Global Settings
4. Thou shall only put one trigger per object. Salesforce allows us to have
multiple triggers per object / table, but there is no guarantee on the order
that those triggers get executed, and therefore can lead to inconsistent
behavior or values.
5. Thou shall not put code in triggers other than calling methods and managing
execution order. In other words, keep as much logic inside your class, and
outside of your trigger as possible. Your trigger should contain things such
as Trigger.isBefore, Trigger.isAfter, etc and invoking class methods.
6. Thou shall utilize maps for queries wherever possible.
7. Thou shall make use of relationships to reduce queries wherever possible.
8. Thou shall aim for 100% test coverage. In general, test your methods for
a. positive effects:
1. Given proper input, it should act like ....
2. Not just happy path, but all logic branches
b. negative effects:
1. Given bad data, it should error.
c. Role / Profile / User effects:
1. Given a user with X profile, and Y role, it shoul act like ...
9. Thou shall write meaningful and useful tests. System.assert,
System.assertEqual, System.assertNotEqual. Test one thing at a time.
Maintain focus.
10. Thou shall limit future calls and use asynchronous code where possible. In
general, bias towards batch Apex method. Ensure that it runs as efficiently
as possible. Limit and tune timeouts for callouts. Tune SOQL queries for
efficiency. If you need @future methods, optimize them the same way you would
optimize your batch Apex. Resist methods that would queue many @future calls
at once due to governor limits.
11. Thou shall streamline multiple triggers on the same object. It is important
to avoid redundancies and inefficiencies when deploying multiple triggers on
the same object. If developed independently, it is possible to have redundant
queries that query the same dataset or possibly have redundant for statements.
We do not have any explicit control over which trigger gets initiated first.
Each trigger that is invoked does not get its own governor limits. Instead,
all code that is processed, including the additional triggers, share those
available resources. So instead of only the one trigger getting a maximum of
100 queries, all triggers on that same object will share those 100 queries.
That is why it is critical to ensure that the multiple triggers are efficient
and no redundancies exist.
12. Avoid hard-coding IDs
// Avoid hard-coding IDs:
Now, to properly handle the dynamic nature of the record type IDs, the following
example queries for the record types in the code, stores the dataset in a map
collection for easy retrieval, and ultimately avoids any hardcoding.
Hard-coding IDs will work fine in the specific environment in which the code
was developed, but if this code were to be installed in a separate org (ie. as
part of an AppExchange package), there is no guarantee that the record type
identifiers will be the same.
// Query for the Account record types
List<RecordType> rtypes = [
Select Name, Id
From RecordType
where sObjectType='Account' and isActive=true
];
// Create a map between the Record Type Name and Id for easy retrieval
Map<String,String> accountRecordTypes = new Map<String,String>{};
for (RecordType rt: rtypes) {
accountRecordTypes.put(rt.Name,rt.Id);
}
for (Account a: Trigger.new) {
// Use the Map collection to dynamically retrieve the Record Type Id
// Avoid hardcoding Ids in the Apex code
if (a.RecordTypeId == accountRecordTypes.get('Healthcare')) {
//do some logic here.....
} else if (a.RecordTypeId==accountRecordTypes.get('High Tech')) {
//do some logic here for a different record type...
}
}
Bad code samples:
trigger accountTestTrggr on Account (before insert, before update) {
//For loop to iterate through all the incoming Account records
for(Account a: Trigger.new) {
// THIS FOLLOWING QUERY IS INEFFICIENT AND DOESN'T SCALE
// Since the SOQL Query for related Contacts is within the FOR loop,
// if this trigger is initiated with more than 100 records, the trigger
// will exceed the trigger governor limit of maximum 100 SOQL Queries.
List<Contact> contacts = [select id, salutation, firstname, lastname, email
from Contact where accountId = :a.Id];
for (Contact c: contacts) {
System.debug('Contact Id[' + c.Id + '], FirstName[' + c.firstname + '],
LastName[' + c.lastname +']');
c.Description = c.salutation + ' ' + c.firstName + ' ' + c.lastname;
// THIS FOLLOWING DML STATEMENT IS INEFFICIENT AND DOESN'T SCALE
// Since the UPDATE dml operation is within the FOR loop, if this trigger
// is initiated with more than 150 records, the trigger will exceed the
// trigger governor limit of 150 DML Operations maximum.
update c;
}
}
}
trigger accountTrigger on Account (before delete, before insert, before update) {
// This code inefficiently queries the Opportunity object in two seperate
// queries
List<Opportunity> opptysClosedLost = [
select id, name, closedate, stagename
from Opportunity
where accountId IN :Trigger.newMap.keySet() and StageName='Closed - Lost'
];
List<Opportunity> opptysClosedWon = [
select id, name, closedate, stagename
from Opportunity
where accountId IN :Trigger.newMap.keySet() and StageName='Closed - Won'
];
for (Account a : Trigger.new) {
// This code inefficiently has two inner FOR loops
// Redundantly processes the List of Opportunity Lost
for (Opportunity o: opptysClosedLost) {
if(o.accountid == a.id) {
System.debug('Do more logic here...');
}
}
// Redundantly processes the List of Opportunity Won
for (Opportunity o: opptysClosedWon) {
if(o.accountid == a.id) {
System.debug('Do more logic here...');
}
}
}
}
page revision: 5, last edited: 23 Nov 2016 23:30