Skip to content

SF Best Practices Documentation

Coding With The Force edited this page Sep 14, 2020 · 24 revisions

Written By: Matt Gerry

This document will outline the standards and best practices I have found to be most useful throughout my time as a SF developer. These are in addition to the typical Salesforce best practices.

I highly suggest you read the following books:

Advanced Apex Programming

Salesforce Lightning Platform Enterprise Architecture - Third Edition


Naming Conventions

All conventions outlined below apply to both apex and javascript. Please ensure your javascript follows all these conventions as well.

Class file naming

  • Prefix classes with business unit specific logic with appropriate business unit namespaces: BusinessUnit_ClassName. to make it easier to identify code for a particular business unit.

  • Prefix classes that are to be utilized as utility classes org wide (without respect to any particular business unit) with the word Util: Util_ClassName. This makes it easier to identify utilities that can be leveraged by everyone working in the org and helps reduce code duplication.

  • Use camel case for class names. All words in a class name should be capitalized.

  • Always append test classes with _Test, this will make classes and test classes sort next to each other in file lists.

  • Class names MUST BE MEANINGFUL! Please ensure the name of your class is relevant to the class and what its purpose is.

BusinessUnit_MeaningfulClassName
BusinessUnit_MeaningfulClassName_Test

Method naming conventions

Methods should utilize camel case conventions. The first word in the method should be lower case, all other uppercase. Method names MUST BE MEANINGFUL. They must be named something relevant to the operation they are attempting to execute.

public void methodName(String acctName)
{

}

Variable naming conventions

Variables should utilize camel case conventions. The first word in the variable should be lower case, all other uppercase. Variable names MUST BE MEANINGFUL they should never be a single character or named something not relevant.

String acctName = 'the name of an account';

Code Formatting

All of the below applies to both apex and javascript.

Brackets

Always place brackets on their own lines, never place them on the same line. It allows the code to be more readable and for bracket related issues to be easily resolved in any IDE.

Also, PLEASE WRAP ALL IF STATEMENTS IN BRACKETS! I know that this is not always necessary but from a readability and debugging stand point it makes things ten times easier. Please always use brackets for if statements.

Bracket Examples:

//Class brackets
public class Brackets
{
  //Code here
}

//Method brackets
public void insertAccounts(List<Account> accounts)
{
  insert accounts;
}

//if statement, case blocks, etc. brackets
if(needsABracket)
{

}

If Statements or Switch Statements or Ternary Operators?

Please opt to use if statements over ternary operators. I know ternary operators are simpler to write, but they make code very difficult to read. If statements are very readable even for junior developers where as ternary operators can confuse many developers and take longer to read.

I also prefer if statements over switch statements. I believe if statements are still much more readable.

Boolean shouldBeChecked = null;

//If else example.
//More lines, but very readable
if(checkIt == 'yea')
{
   shouldBeChecked = true;
}
else
{
    shouldBeChecked = false;
}

//Switch statement example
//Readable but less intuitive in my opinion.
switch on checkIt
{
    when 'yea'
    {
        shouldBeChecked = true;
    }
    else
    {
        shouldBeChecked = false;
    }
}

//Ternary operator example.
//One line but very confusing.
shouldBeChecked = checkIt == 'yea' ? true : false;

Space or Tabs

All of the below applies to both apex and javascript.

You had better use tabs dawg. If you use spaces you will not be forgiven. If you don't use proper tab indentation you will not be forgiven.


Comments

As a general rule you should always comment your code. It's important to document the following things:

  1. Why the code was written in the first place. What were the business reasons behind it? What is the class/method actually made to do? Etc.
  2. To document complex code and why it has to be complex. Typically code is only complex because of inadequate architecture decisions resulting in loosely coupled data (or something similar to that). It still happens though and when it happens it's important to document that in the code.
  • Always Use ApexDocs Comment Formatting

ApexDocs is a wonderful thing. If it's utilized appropriately you will always have exceptional code documentation and it can all be generated automatically. Please make sure all of your code comments follow the ApexDocs format so that you can auto generate markdown files for wiki documentation for our codebase.

For information on how to easily install apexDocs please check out my wiki article here: How to Install ApexDocs

  • Class Comment block

At the top of each class create a comment block that notes the developer name (your email), date and a short description of what the class does, why it was made and where it may be referenced elsewhere in the code. Also be sure to include your JIRA ticket number in the description. If it is the controller for an aura component, vf page or LWC you should state what component it is a controller for.

/**
 * @author matt.gerry@tangocentral.com
 * @date 10/23/2019
 * @description Explains coding standards and best practices. JIRA Ticket: #4444
 */
  • Method comments

Above every method there should be a description as to what it does and how it is utilized. It should also contain useful comments about the method. Comments should inform the developers as to why something was coded the way it was and the business justifications for it.

/**
 * @description A description of the method.
 * @param exampleVariable Information about the variable we pass to the method.
 * @return What return do we get from this method and why.
 * @example
 * Boolean exampleBoolean = ExampleClass.exampleMethod('exampleString');
 */
public static Boolean exampleMethod(String exampleVariable)
{
    //Logic and code comments to help understand the business reasoning behind the development
    //choices that were made.
}

CODE COMMENTS ARE EXTREMELY IMPORTANT!! YOU WILL FORGET WHAT CODE WAS MADE FOR AND HOW IT WORKS WITHOUT THEM!!


HTML Guidelines and Formatting

Formatting

HTML formatting should be done in a similar format to how apex indentation is done to improve readability. The code commenting should be the same as apex or javascript.

<!--
     Written By: Matt.Gerry@tacotastic.com
     JIRA Ticket #4444
     Date: 11/4/2019
     Description: Example html tab indentations
-->

<html>
    <head>
        <script></script>
    </head>

    <div>
        <table>
        </table>
    </div>
</html>

Javascript

  • If the javascript is not contained in an Aura Component or an LWC, Javascript should be housed in its own static resource file and added to the code via a script tag.

  • Javascript should be formatted and commented using the exact same standards as outlined above for apex.

CSS

  • CSS should always be housed in its own stylesheet file and referenced in the vf page or Aura component or LWC.

  • CSS should never be inline or written directly into the page (except for in exceptionally rare circumstances relating to visualforce to pdf rendering).


Test Classes

Test method best practices

  • Use testMethod modifier on test methods instead of @isTest above the method signature. It looks quite a bit cleaner.
  • Put the test setup method as the first method in the test class before all test methods. Also, always utilize @TestSetup methods for setting up test data.
  • Create a test method for every class method no matter what, even if one method will call to another method and give you code coverage.
  • Always ensure every test method uses at least one assertion to make sure we are not only getting code coverage but that our apex classes are returning the results we expect them to return.
  • Make sure to test for both positive and negative scenarios in your test classes (scenarios that fail and scenarios that don't).
  • Make sure to test bulk scenarios. Testing the creation of one contact in a contact trigger is not sufficient. Test how it handles a load of 10,000 contacts too.
//Test Class
@isTest
public class ExampleClass_Test
{
    @TestSetup
    public static void setupData()
    {
        //setup all data that you will need in multiple test methods here
        //The test setup method has its own set of dml limits and will not affect the dml limits
        //of the other methods. Always utilize the object creator for test classes.
List<User> rmdmUser = ObjectCreator.userCreator('MattyRMDM', 1, null, roleId);
    }

    public static testMethod void methodOne_ValidScenario_Test()
    {
        //This test method tests a valid scenario for the method named "Method One" in our
        //"ExampleClass" apex class.
        //All data setup should happen first in this method by utilizing the object creator class
        Test.startTest();
            //The actual callout to the method should always happen inside a Test.startTest() and
            //Test.stopTest() block. This ensures that all system limits are reset and that we
            //have a clearer picture as to what system limits this method is actually reaching.
        Test.stopTest();

        //After we do our test we should always do a System.assertEquals to ensure we aren't just
        //getting code covered but that we are actually getting the results we expect.
        System.assertEquals(value1, value2);
    }
}
  • Use the SetupData method to create test data, put it before test methods. Separate setup and non-setup object data to avoid mixed dml statement errors.

  • Always use a data factory class to create test data, never build that in the class itself.

  • Make sure your data factory can create bulk amounts of data for testing, not just a single object at a time.

TestDataFactory.createUsers() ...
  • Use start test and stop test. This will make sure that limits used by the data setup are not counted against the test. This resets and provides a separate set of limits for the test to use.
Test.startTest();
  //Some code goes here
Test.stopTest();
  • Use assertions to verify the expected outcome has happened
System.assertEquals(expected, actual);

Triggers


Triggers should never house any real logic. Their logic should reside in handler classes. This allows us to better handle issues with logic and identify where a problem resides.

I suggest utilizing the sfdc-trigger-framework. It's super light weight and more than enough for the vast majority of orgs.

THERE SHOULD NEVER BE MORE THAN ONE TRIGGER PER OBJECT!!!!

Trigger Structure

Triggers operate in the following way.

  1. They run Before operations, then after operations. Before and after operations occur within the same context.
  2. They always operate in chunks of 200 records. So if you send in a batch of 1000 contact updates at the same time, the trigger will fire 5 times. It fires once for every 200 records. This all occurs within the same context.
  3. Triggers are re-invoked on record update or record insert actions from flows, workflows and process builders. For this reason it is not ideal to have combined execution types. Either use triggers, flows, workflows or process builders. Combining them can really slow things down and is detrimental in larger orgs. If you do combine them, make sure nothing requires trigger recursion and build mechanisms to shut off the triggers when another process updates or inserts a record.

Please reference the sfdc-trigger-framework documentation to see how to appropriately structure your triggers and their handler classes.

Trigger Recursion

TRIGGERS SHOULD NEVER REQUIRE RECURSION If your trigger requires recursion, you have designed your implementation wrong, there is never a need for recursion to happen within a trigger and can have detrimental effects on the environment as a whole.

That being said there are lots of things that can cause a trigger to re-fire such as a process builder updating or inserting a record or a workflow rule updating a record.

To prevent workflows and process builders from recursively firing a trigger we should always implement a way to prevent the recursion from taking place. With the sfdc-trigger-framework you can utilize the bypass method to bypass your trigger when appropriate.

In the event you have a process builder or workflow on an object that is causing trigger recursion use the apex utility classes provided on this github page to bypass the trigger execution.

Trigger Switches

  • Needs to be updated.



Process Builders


Process builders are much like triggers in that there should typically only ever be one of them, however on occasion there can be a maximum of two per object. DO NOT MAKE MORE THAN ONE PROCESS BUILDER FOR EACH OF THE TWO TYPES LISTED BELOW! Having more than one for either type can cause unexpected consequences. Build supplemental process builder logic into an existing process builder.

Naming Conventions

Process builders should be named as follows:

Insert Process Builders: [ObjectName] - Record Insert Insert/Update Process Builders: [ObjectName] - Record Update

[ObjectName] should be replaced with the name of the object the process builder is on.

Insert Process Builders

The first of the two types of process builders is an insert process builder. These process builders are used when an object has actions that should be taken on it ONLY when an new record is created for it. So if you specify in the process builder "Only when a record is created" That would qualify as an insert process builder.

Insert/Update Process Builders

The second and most common type of process builder is a process builder that operates on insert and on update. If you make a process builder with the "when a record is created or edited" criteria it would qualify as an Insert/Update process builder.

Terminating a Process Builder

In the event you would like entry criteria for a process builder to terminate all actions the process builder may take without taking any action on any records, please utilize the Process_Builder_Terminator apex class. Use an apex callout to call the Process_Builder_Terminator to end your process builder without doing anything at all.

Process Builder Switches

  • Needs to be updated

Workflow Rules

Aside from time based event firing, there is really no need to utilize these much anymore. Anything they are capable of can typically be achieved within a process builder or a process builder combined with a flow or an apex class. IF A PROCESS BUILDER CAN DO IT, USE A PROCESS BUILDER PLEASE.



Error Logging

How to utilize the custom Error Log Table:

ALL CODE MUST UTILIZE THE ERROR LOGGING TABLE!! This custom error logging table allows us greater insight into the errors that occur, when they occurred and who they occurred for. The error logging should be implemented in both the view and controller (view could be the aura component, LWC or VF Page). You should call the Universal_Error_Logger apex class to insert these error logs.

The custom error logging tables developer name is Error_Log__c. You need to fill out the following fields in the following ways:

  • Displayed_Error__c : The error that was actually displayed to the user (this applies if you have custom error messaging that was simplified for the end user).
  • Page_Error_Occured__c: This should be the name of the view or controller that the error occurred on.
  • SF_Error_Detail__c: This should be the true system generated error that happened.
  • User__c: This field should always be filled out with the Id of the operating user which you can get by using the UserInfo.getUserId() method.

Apex Example:

public String insertAcctRecord(Account acct)
{
    String returnUIMsg = null;  

    try
    {
        database.insert(acct);
        returnUIMsg = 'Account Successfully Inserted';
    }
    catch(Exception ex)
    {
        returnUIMsg = 'There was an error inserting your account';
        Universal_Error_Logger.auraGenerateErrorLog(returnUIMsg, 'insertAcctRecord',
        ex.getMessage());
    }

    return retunUIMsg;
}

Aura Example

There is a component named UniversalErrorLogger that we will add to our component to allow this to be relatively abstract.

Component

<aura:component description="Example Component" controller="exampleController">

    <!-- Here we are importing the universal error logging component
     so that we can record errors when they happen in our error logging
     object/table and inform admins-->
    <c:UniversalErrorLogger aura:id="errorLogger"/>

    <aura:attribute name="errorList" type="List"/>

</aura:component>
exampleMethod : function(component, event, helper)
{
        var exampleCallout = component.get("c.controllerMethod");
        exampleCallout.setCallback(this, function(response)
        {
            var state = response.getState();
            if(state === 'SUCCESS')
            {
                var returnVal = response.getReturnValue();
                console.log('Return Value ::: ' + returnVal);
            }
            else if(state == 'ERROR')
            {
                component.set("v.errorList", response.getError());
                helper.sendErrorToAdmins(component, event, helper);
            }
        });
        $A.enqueueAction(addCRMember);
    },

    sendErrorToAdmins : function(component, event, helper)
    {
        //Getting our errors and feeding them into a string (while it's possible multiple errors can be returned
        //the first error is typically the most useful error). If we find we need to return more info we can expand this later.
        var errorsCombined = '';
        var errorList = component.get("v.errorList");
        if (errorList)
        {
            if (errorList[0] && errorList[0].message)
            {
                errorsCombined = errorList[0].message;
                console.log("Error message: " + errorList[0].message);
            }
            else
            {
                errorsCombined = 'Unknown server error';
            }
        }

        //Finding our UniversalErrorLogger component using the aura:id. The reference to this child component is in the CRM_Mapping_Tool component at the top of the page.
        //We use that reference here to grab it
        var errorComponent = component.find("errorLogger");

        //Calling the submitErrorLog method inside the UniversalErrorLogger components component and passing it variables
        errorComponent.submitErrorLog(errorsCombined, 'ERROR', 'ExampleComponent');
    }

Security

Apex With Sharing

Aside from very specific use cases, please do your best to always utilize the "with sharing keyword in your apex classes to ensure that the internal sharing rules for the user currently running the code are enforced.

//Sharing rules enforced for the use
public with sharing class exampleClass
{

}

Using the sObject Describe Class for increased security:

In practice I find this is really only necessary in several situations.

  • When doing VF Remoting
  • Calling controllers that don't have with sharing enabled from aura or lightning components.
  • When most of an apex controller is required to operate in system context but some pieces need enhanced security to proceed.

There are probably more scenarios out there, but these have been the most prevalent for me. If you find yourself in any of those situations, then you need to utilize the sObject describe class to secure your data.

@RemoteAction
webservice static String insertAccount(Account acct)
{
    // checks to see if the running user has permissions to create an account
    if(Schema.sObject.Account.isCreateable())
    {
        database.insert(acct);
    }
}

@RemoteAction
webservice static String updateAccount(Account acct)
{
    // checks to see if the running user has permissions to update an account
    if(Schema.sObject.Account.isUpdateable())
    {
        database.update(acct);
    }
}

@RemoteAction
webservice static String deleteAccount(Account acct)
{
    // checks to see if the running user has permissions to delete an account
    if(Schema.sObject.Account.isDeletable())
    {
        database.delete(acct);
    }
}

@RemoteAction
webservice static String getAccount()
{
    // checks to see if the running user has permissions to read an account
    if(Schema.sObject.Account.isAccessible())
    {
        Account acct = [SELECT Id FROM Account LIMIT 1];
        return acct;
    }
}

Preventing SOQL Injections

ALL INPUT FROM AN END USER OR INPUT THAT COULD BE TAMPERED WITH BY AN END USER NEED TO BE SECURED IN THE SERVER SIDE CONTROLLER CODE!!! So let's talk about how to do that shall we?????

The best way to prevent SOQL inject is to utilize the escapeSingleQuotes method on variables that have been passed from the client side to the server side.

@AuraEnabled
public static List<Account> getSomeAccounts(String acctName, String acctState)
{
    //Escaping all quotes to make sure SOQL injection can't happen from user provided input.
    String escapedAcctName = String.escapeSingleQuotes(acctName);
    String escapedAcctState = String.escapeSingleQuotes(acctState);

    List<Account> acctsToReturn = [SELECT Id FROM Account WHERE Name = :escapedAcctName AND BillingState = :escapedAcctState];
}

More Secure Coding Stuff

There are literally tons and tons of things I could write up about this but I'm getting tired. Please ensure you follow all of Salesforce's secure coding guidelines when you are developing. It is extremely important we don't open ourselves up to attacks.

Salesforce Coding Guidelines


General Coding Guidelines/Principles

  • No SOQL queries inside loops.
  • Opt for multiple queries rather than inner queries (in most cases multiple queries are better).
  • Utilize Maps instead of nested for loops. Nested for loops drastically decrease operational speeds.
  • Don't expose critical information to the client side (aka no critical info passed to a javascript controller). All critical information should be housed in a server side controller.
  • Any input from a user should be processed by a controller to ensure no malicious activity can make it through. For example any user input or any value (including url variables) that can be altered by a user that is utilized in a soql query should have the escapeSingleQuotes method utilized on it to ensure SOQL injections can't occur.
  • Utilize the "with sharing" keyword on your apex classes to make sure that end users can only access what they are supposed to access.
  • Custom metadata should be used to store connection credentials for API integrations.
  • Custom Settings should be leveraged to make code extremely dynamic and malleable.
  • Make sure your code is abstract enough for the future. If your code can't change at the speed of light, you have coded it wrong. If it's not dynamic enough all of our lives will eventually become a nightmare.

Clone this wiki locally