Apex Best Practices: The 15 Apex Commandments
Follow these 15 Apex Commandments for developing applications with Apex code and Visualforce.
— Coauthored with Kevin Poorman
Have you ever encountered a governor limit exception? Posted a question on the developer forums seeking best practices? Wondered the best approach for writing a unit test, or why some customizations work better than others on Salesforce’s multi-tenant architecture? Follow along, as we’ll discuss fifteen guidelines you should consider when customizing your next Salesforce application.
#1 Thou Shalt Keep thy code stupid simple (KISS principle)
In software development, the applying the KISS principle means your code is kept clean and concise. There should be as little logic in your code as possible. Complex if else statements? Usually they can reduce to simple boolean logic. Reusing code in several places? Write a single method and reduce the clutter in your code. Simple code is easier to maintain. Always use the KISS principle and keep your Apex stupid simple.
Example of cluttered code with unncessary logic
Example of cleaning the cluttered code above and applying the KISS principle
#2 Thou shalt not put queries in loops
One way Salesforce enforces that its tenant’s applications or your applications must run in a performant manner. One way Salesforce does this is by enforcing governor limits. One of the most stringent governor limits is a SOQL query limit of 100 queries in a transaction. If you ever query inside a loop, you will run into this limitation. Hence, thou shalt not put queries in for loops.
Consider the following trigger, which could run in batches of 200 records. When the 101st query is executed, Salesforce throws an uncatchable LimitException. Your trigger fails and any work your trigger has performed is rolled back.
Bad example of a SOQL query inside a for loop
Instead, you should either make use of maps or relationship queries to consolidate the transaction into one SOQL query, outside of the for loop.
Example of utilizing relationships to keep SOQL outside of a loop
SOQL queries are one of many Apex tools that have governor limits. For more information on governor limits, see Understanding Execution Governors and Limits.
#3 Thou shalt utilize maps for queries
As demonstrated in the example above, utilizing maps for queries, is a fundamental technique and trick to use in order to make SOQL queries, DML transactions, and Apex triggers more efficient. Sometimes, you’ll be refactoring some code and come across a query as a List. Thankfully, there’s a trick to getting a Map from a list, without a For Loop. Check this out:
Map<Id, sObject> myAwesomeMap = new Map<Id,sObject>(List of sObjects or SOQL Query);
The code above gives you a map, without iteration from a pre-existing list of sObjects!
#4 Thou shalt use relationships to reduce queries
Another key technique in making SOQL queries more efficient is to utilize relationships. In the example of utilizing relationships to keep SOQL outside a for loop, we utilized the children Contacts relationship of the standard Account object. SOQL also gives us the ability to query for parent record information, such as in this example. For more information on utilizing relationships in SOQL, read and bookmark Salesforce’s article on Relationship Queries
#5 Thou shalt not put DML in loops
Similar to the 2nd commandment, thou shalt not put DML in loops either. As a quick recap, DML means any insert, update, delete, undelete, merge, convertLead transaction. Not only is DML in a loop poorly performant, but you will run into governor limits as well. To avoid DML in for loops, generally speaking, make sure you’re acting on multiple records.
The following example demonstrates how a list is used to keep track of accounts that need to be updated. Once the loop is finished, a single DML transaction is performed to update the list of accounts.
Example of performing DML outside of a loop
#6 Thou shalt only use one trigger per object
How many Account triggers does your org currently have? 1? 5? 10? More? Generally speaking, multiple triggers across the same object greatly increase the complexity of your Salesforce application. Furthermore, one trigger is not guaranteed to fire before another trigger. By consolidating any triggers, your application is less prone to mistakes and defects, and business processes are much easier to maintain.
#7 Thou shalt keep logic outside of triggers
Triggers should only contain a handful of lines of code, used for calling methods. The trigger itself should only be used for managing execution order, and delegate all else to a separate Apex class such as a trigger handler. For an example of a trigger handler pattern, check out Force.com MVP Kevin O’Hara’s SFDC Trigger Framework. Also, when you’re managing the execution order in your trigger, keep in mind the order of execution for other Salesforce components.
#8 Thou shalt have a happy balance between clicks and code
Salesforce provides a tremendous amount of declarative functionality with workflow, flow, validation rules, formulas, and more. Knowing when to use those declarative features rather than jumping straight into an Apex or Visualforce solution separates the great developers from the rest of the pack. This is especially important as the next few releases are bringing some amazing declarative functionality, including Process Builder. Process Builder has the capability to declaratively build simple triggers – eliminating the need to code them!
#9 Thou shalt cover thy code
You should aim for 100% code coverage. Even though Salesforce mandates 75% code coverage across your apex classes when you deploy to production, you should always aim for 100%. This is not to say you cannot leave work until your class covers that last 3%, but the goal being as high a code coverage as is sensible. Remember that unit tests are not just for Salesforce’s benefit, but your own – establishing peace of mind that your application is functioning as expected
#10 Thou shalt write meaningful unit tests
Even though your code is 100% covered, it does not necessarily mean nor assert that your code is working properly. In order to verify your code is working and no false positives are returned by the unit test, use System.assert statements liberally. After all, a unit test is not a test without assertions. Below are some methods Salesforce provides for asserting unit tests:
- System.assert(A == B,’A does not equal B’);
- System.assertEquals(Expected,Actual,’Actual does not equal Expected’);
- System.assertNotEquals(Expected,Actual,’Actual equals Expected, but should not’);
For bonus points, utilize that final parameter to leave yourself meaningful, descriptive statements about why your test fails when the expected and actual values do not match.
#11 Thou shalt write unit tests before developing
Knowing that you must write unit tests in order to deploy your code, why not write out your unit tests ahead of time? Writing out unit tests provides additional forethought into what the application does, how it handles different conditions, and how the application will handle various conditions – especially exception cases.
#12 Thou shalt test all conditions
Once your tests are written, your code is 100% covered, you’re ready to deploy, right? Not necessarily. Have you tested all the conditions with those tests? Check for nulls, boundary conditions, assert that your exceptions were thrown or handled properly? A good test class will test all these conditions and more, and provide further confidence that your application is working as designed.
#13 Thou shalt never use dummy code coverage
One way into getting around the 75% code coverage feature is to generate classes with useless lines of code such as i++. These dummy code coverage classes do more harm than good. Dummy code coverage gives a false sense of security around the application, and only allows additional defects to sneak through the cracks when testing the application. Dummy code coverage makes Astro cry, and you don’t want Astro to cry, do you?
#14 Thou shalt never test with existing data
One of my favorite questions across the developer forums goes something along the lines of this:
“My unit test has been working fine in production for years. All of a sudden, it’s throwing an exception and now I can’t deploy my code, please help”
This is typically a result of relying on existing data in your Salesforce instance. Because the test data relies on existing data, it will fail once the dependent record(s) are deleted from the System. Because the test fails, so do any subsequent deployments. The remedy to this situation is to always create mock data and never use the @isTest(SeeAllData=true) annotation in unit tests nor rely on existing data.
#15 Thou shalt not introduce extra logic for tests
It used to be that developers needed to use Test.isRunningTest or other logic to avoid making callouts and handling exceptions in their unit tests. With the advent of the HttpMock and WebserviceMock interfaces, there is little need for the Test.isRunningTest() method. If there is still a need for injecting additional code for unit tests, then consider looking at the @TestVisible annotation instead.
The broken tablet above discusses principles derived from unit testing best practices. If you’re interested in learning more about unit testing or other facets of software development, be sure to check out the book “Beautiful Code: Leading Programmers Explain How They Think.”
Congratulations! You’ve read Astro’s 15 Apex Commandments. Do you have any commandments you or your colleagues follow? Be sure to comment below.