Advanced Apex Training: Apex Best Practices

23
Advanced Apex Training: Apex Best Practices

Transcript of Advanced Apex Training: Apex Best Practices

Page 1: Advanced Apex Training: Apex Best Practices

Advanced Apex Training: Apex Best Practices

Page 2: Advanced Apex Training: Apex Best Practices

This article dives into what we, developers at Kairos Technologies, recently learned as we attended ‘Apex Best Practices’ training session at our Global Delivery Center, Hyderabad. This is one of many advanced topics the company organized for all Salesforce developers as part of the continuous improvement and learning. I took some time to write this blog to reflect on all the things I learned (with some of code samples) from our Senior Architect and I hope this will help other developers too. For more details, please watch this awesome video ‘Winter ’16 Release – Overview & Highlights‘.

Follow along, as I show you all the guidelines you should consider when writing code for your next Salesforce applications.

Page 3: Advanced Apex Training: Apex Best Practices

Governor Limits

Why governor limits?

Force.com is built on a multi-tenant Architecture. In order to ensure that Apex code of a single tenant does not consume significant amount of shared resources on Force.com platform and to ensure every tenant gets equal share of the platform resources.

Per-transaction Apex Limits

Total # of SOQL Queries: 100 (Sync) | 200 (Async)Total # of records retrieved: 50KTotal # of DML statements: 150Total # of records processed by DML: 10KTotal heap size: 6MB (Sync) | 12MB (Async)Maximum SOQL query runtime before Salesforce cancels the transaction: 120 seconds

Page 4: Advanced Apex Training: Apex Best Practices

SOQLAvoid writing non-selective queries except during batch processing. Avoid using only negative operators (!=)

Use Primary Keys, Foreign Keys, CreatedDate, LastModifiedDate, External ID or Unique Fields in query filters.

Example for Batch Processing : Mass Delete of Records

Bad Query (When record count exceeds 100K)

SELECT ID, NAME FROM ACCOUNT

SELECT ID, NAME FROM ACCOUNT WHERE NAME != ‘ ‘

System.QueryException: Non-selective query against large object type (more than 100k rows). Consider an indexed filter or contact salesforce.com about custom indexing

Page 5: Advanced Apex Training: Apex Best Practices

Good Queries

SELECT ID, NAME FROM ACCOUNT LIMIT 5

SELECT ID, NAME FROM ACCOUNT WHERE ID IN :IDS (Only when ID list is small)

SELECT ID, NAME FROM ACCOUNT WHERE NAME != ‘’ AND OWNERID IN :OID

What happens if I dont write selective queries?

Salesforce could throw a runtime error like this:

System.QueryException: Non-selective query against large object type (more than 100k rows). Consider an indexed filter or contact salesforce.com about custom indexing

Page 6: Advanced Apex Training: Apex Best Practices

Best Practices for Apex Classes

Apex code must provide proper exception handling.

When querying large data sets, use a SOQL “for” loop.

We should not use SOQL or SOSL queries inside loops.

Apex code must provide proper exception handling.

We should prevent SOQL and SOSL injection attacks by using static queries, binding variables or escape single quotes method.

Should use SOSL rather than SOQL where possible.

Page 7: Advanced Apex Training: Apex Best Practices

Should not use DML statements inside loops.

Should not use Async(@future) methods inside loops.

Should use Asynchronous Apex(@future annotation) for logic that does not be executed synchronous.

Asynchronous Apex should be Bulkified.

We should not use hardcoded IDs as they may cause problem when moved to production.

Page 8: Advanced Apex Training: Apex Best Practices

Graceful Design of the CodeBAD CODE:public class CustomCalendarUtil {public static String getMonth(Integer month){String varMonth = ‘’;if(month==1){varMonth = ‘January’;} else if(month==2){varMonth = ‘February’;}....return varMonth;}}This is functionally correct code but is a maintenance overhead dueTo many if else statements.

Page 9: Advanced Apex Training: Apex Best Practices

GOOD CODE:

public class CustomCalendarUtil {static Map<Integer, String> monthMap = new Map<Integer, String>();private static void init(){monthMap.put(1, ‘January’);monthMap.put(2, ‘February’);monthMap.put(3, ‘March’);}public String getMonth(Integer month){return monthMap.get(month);}}

Page 10: Advanced Apex Training: Apex Best Practices

Use static methods, avoid storing state

BAD CODE

public class CustomCalendarUtil {

Map<Integer, String> monthMap = new Map<Integer, String>();

private void init(){monthMap.put(1, ‘January’);monthMap.put(2, ‘February’);monthMap.put(3, ‘March’);}public String getMonth(Integer month){init();return monthMap.get(month);}}

Page 11: Advanced Apex Training: Apex Best Practices

GOOD CODE

public class CustomCalendarUtil {

static Map<Integer, String> monthMap = new Map<Integer, String>();

static {monthMap.put(1, ‘January’);monthMap.put(2, ‘February’);monthMap.put(3, ‘March’);

}public static String getMonth(Integer month){return monthMap.get(month);}}

Page 12: Advanced Apex Training: Apex Best Practices

Best Practices for Triggers:

Execute DML statements using collections instead of individual records per DML statement.

Should use collections in SOQL “WHERE” clauses to retrieve all records back in single query.

Use a consistent naming convention including Object name(ex: contactTrigger)

Best to have one Trigger to each object.

Complex logics should be avoided to simplify testing and reuse.

Bulkify “helper” classes or methods.

Trigger should be bulkified and be able to process up to 200 records for each call.

Page 13: Advanced Apex Training: Apex Best Practices

Avoid SOQL inside for loop

BAD CODE:

trigger GetContacts on Accounts (after insert, after update) {for(Account a : Trigger.new){List c = [SELECT Id FROM Contact WHERE AccountId = a.Id];}}

GOOD CODE:

trigger GetContacts on Accounts (after insert, after update) {Set ids = Trigger.newMap.keySet();List c = [SELECT Id FROM Contact WHERE AccountId in :ids];}

Page 14: Advanced Apex Training: Apex Best Practices

Avoid DML operations in for loop

BAD CODE:

trigger UpdateContacts on Accounts (after insert, after update) {for(Account a : Trigger.new){List cl = [SELECT Id, LastName FROM ContactWHERE AccountId = a.Id];for (Contact c: cl){c.LastName = c.LastName.toUpperCase();}

UPDATE cl;

}}

Page 15: Advanced Apex Training: Apex Best Practices

GOOD CODE:

trigger UpdateContacts on Accounts (after insert, after update) {Set ids = Trigger.newMap.keySet();List cl = [SELECT Id, LastName FROM ContactWHERE AccountId in :ids];for(Contact c: cl){c.LastName = c.LastName.toUpperCase();}

UPDATE cl;

}

Page 16: Advanced Apex Training: Apex Best Practices

SOQL processing for large datasets

BAD CODE:

List accounts = [Select Id, name from Account];for(Account a : accounts){Contact c = new Contact();c.AccountId = a.Id;....insert c;}

Salesforce could throw a runtime error like this:-

Caused by: System.DmlException: Insert failed. First exception on row 0; first error:Caused by: System.LimitException: Apex heap size too large: 7522771

Page 17: Advanced Apex Training: Apex Best Practices

GOOD CODE:

for(Account account : [Select Id, name from Account]){//do something}

Each iteration returns 200 records. However avoid performing any DML or SOQL operations inside the loop.

Heap Issue can be avoided.

However, this kind of coding can be avoided altogether depending uponThe exact requirement

Page 18: Advanced Apex Training: Apex Best Practices

Best Practices for Testing:

Developers/Testers should always remember to write comprehensive tests to cover as many scenarios as possible. 75% code coverage is good for deployment but not necessarily guarantee a bug-free code deliverables. We should target for 100% code coverage.

Cover as many lines of code as possible.

Comments should always be written.

Should create test data in test classes and should not use existing records.

Avoid using SeeAllData=true whenever possible.

Call methods using both valid and invalid inputs.

When we write conditional logic, write tests to execute each branch of code logic.

Page 19: Advanced Apex Training: Apex Best Practices

Test if trigger can handle bulk load.

Hard coding of IDs should be avoided.

Test classes should be written along with code rather than waiting till the end.

Should follow naming conventions for test classes

Ex : For updateContact class,create a test class updateContactTest instead of TestUpdateContact.

Page 20: Advanced Apex Training: Apex Best Practices

Test Class

INCOMPLETE:

@isTestpublic class CustomCalendarUtilTest {

static testMethod void testGetMonth(){String m = CustomCalendarUtil.getMonth(1);System.assertEquals(‘January’,m);}

static testMethod void testGetMonth(){String m = CustomCalendarUtil.getMonth(2);System.assertEquals(‘February’,m);

}}

Page 21: Advanced Apex Training: Apex Best Practices

GOOD:

@isTest

public class CustomCalendarUtilTest {

static testMethod void testGetMonthJan(){ String m = CustomCalendarUtil.getMonth(1); System.assertEquals(‘January’, m);

} static testMethod void testGetMonthApril(){ String m = CustomCalendarUtil.getMonth(4); System.assertEquals(‘April’, m); }

Page 22: Advanced Apex Training: Apex Best Practices

static testMethod void testGetMonthInvalid(){

String m = CustomCalendarUtil.getMonth(13); System.assertEquals(‘Invalid Month’, m);

} �}