Salesforce - Developer - Best Practices

salesforce-developer

// 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&nbsp;: 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...');
      }
    }
  }
}
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License