At various stages in the career development of probably every IT specialist, there comes a time when we ask ourselves, “Is what I’m doing actually as good as it could be?” I am convinced that people who pay close attention to the quality of their services or products measure dedication by asking: Am I not ashamed to sign my name to this?

In this article, I have compiled what I believe are the best practices for writing Apex source code as a Salesforce technology developer. Sit back and see if you apply them all.

The name matters

In my opinion, the first and crucial skill that indicates how well you write code is naming its various elements. The issue, which may seem trivial to some members of the programming community, actually plays a crucial role in understanding the overall concept and thought process that the code author has chosen to achieve a specific goal.

Sometimes, you may encounter the opinion that well-written code is like a story someone tells us or like a good book. And before you go any further, please ask yourself: Is this actually not the case?

Case study

Let’s consider a simple scenario: it has been 72 hours since the last modification to the Case record, and the checkbox IsOnHold__c is not checked; close the Case and set the value of OpenCasesNumber__c on the related Account to one less than its current value. The value cannot be less than 0 (zero).

This would be the code that meets the described requirements, but the author doesn’t pay enough attention to the naming of methods and variables:

Code fragment depicting the assignment of a list of Cases from a SELECT statement to a variable named cList. Initializing a Set of Ids named aIdList. Iterating over cList with a local variable named c. Fetching a list of Accounts and assigning them to aList. Assigning the OpenCasesNumber__c value from the currently iterated Account decremented to a variable named ocn

However, this would be the code where the author pays attention to the naming of variables and methods:

Assign the result of a Case list query to the meaningCases variable. Initialize the Set Id named accountIds. Assign the result of the Accounts list query to the linkedAccounts variable. Assign the value of OpenCasesNumber__c decremented from the currently iterated Account to a variable named openCasesNumber

Although calling either code will produce the same result, the latter reads more comfortably, and the names suggest what the method is intended for and what each variable holds. Such code is easier to understand and maintain and shows the “class” of the person who wrote it. I think there are some people who have concerns about whether their code might look too simple. I assure those individuals – it is much worse to overcomplicate things and deliver something that works but is difficult to understand, ultimately making it harder to maintain.

What principles to follow when naming variables, classes, and methods?

At Salesforce, in addition to the general Clean Code rules, such as naming variables and methods using camelCase notation and classes using PascalCase, several additional recommendations are commonly followed and worth applying.

Recommendations for variables are as follows:

  • avoid using generic words, for example, var, variable, temp, value, data,
  • restrict containing the type name of a variable in its name, except for SObjects,
  • choose a name that leaves no doubt about what is contained in the variable,
  • don’t overly abbreviate words – newCase is a good idea, while newC is not; pay attention to this, especially with loops.

[The image shows examples of poor variable naming. Examples: casNum of type Integer, htmlExpertDescriptionValue of type String, triggerContextOpportunity of type Opportunity. The image also shows examples of good variable naming. Examples: caseNumber of type Integer, expertDescription of type String, newOpp of type Opportunity

Recommendations for methods are as follows:

  • choose names that describe well what the method is responsible for,
  • avoid excessive conjunctions in names, for example, “callAndDeserialize“, “getCaseOrNull“,
  • do not combine too many words in the name – 1 to 3 words is the optimal name; we should not exceed 4 words,
  • follow the principle that a good method name describes the action or purpose of the method while maintaining a clear and descriptive expression written in camelCase.

The image shows examples of good and bad method naming. It contains a method called getNewCase, which returns a new instance of the Case type. Wrong example: the method is called getDraftCase, inside the body of the method the getNewCase function is called and the status of the Case is set to Draft, the method returns void. A good example: the method is called getNewDraftCase, in the method body it calls the getNewCase method, sets the Status to Draft by referencing a static field in the SFS_Constants class, and returns an object of type Case

Recommendations for classes are as follows:

  • use the concept of “service classes“, which involves the SObject name with the word Service, for example, CaseService, OpportunityService,
  • suggest in the name whether the class utilizes the “sharing” mechanism: for a class that observes it and operates within Account records, a good idea would be AccountService; otherwise, AccountServiceNoShare,
  • use prefixes when there are multiple projects within one repository.

The image shows an empty class with a key expression with sharing named SFS_CaseService and another empty class with a key expression without sharing named SFS_CaseServiceNoShare

What about hardcoding? Avoid or find a good place for it?

Programming, like everything around us, is not perfect, and – although it would be nice to have code without hard-coded values – instead of fighting the fact of their existence, it is better to find a suitable place for them. A class that contains only constant values will work well in such a situation.

Applying good naming practices, we already know that the name Constants is suitable because it accurately describes its purpose, and the name meets the requirements of PascalCase notation.

Let’s look at the following example:

The image shows a class named SFS_Constants containing static fields with the keyword final. Example: CASE_STATUS_DRAFT with the value Draft assigned during initialization. Another example: CONTRACT_STATUS_DRAFT with the value Draft assigned during initialization

  • As an additional note, sometimes we have exactly the same value but intended for different purposes. As an example, let’s take the Draft status – the same value would be suitable when we want to mark the status on both the Contract object and the Case object. Even though the value is the same, I suggest creating separate constants.

Design patterns and frameworks – principles of good programming

TriggerHandler is a framework that comes in many versions; there are simpler and more complex options – implemented depending on the project’s needs. The advantages of TriggerHandler are as follows:

  • It is highly reusable (to any SObject).
  • It allows a mechanism to enable and disable specific triggers without deploying them to the environment (when you disable them, remember to enable them again).
  • It enables an enforcing model (based on abstract methods) or a voluntary model (based on virtual methods).
  • It allows implementing a module that enables and disables specific triggers from code within a transaction.

Fragment of the TriggerHandler class with the isTriggerEnabled method visible accepting an argument named triggerName of type String. The method retrieves an instance of custom metadata named TriggerDriver. The instance name is the argument of the method. In case of an error, an exception with a custom message is thrown. trigger control mechanism from the organization level

The image shows an AccountTrigger that creates an instance of the AccountrTriggerHandler class in the body and runs the start method, passing as an argument the statically final field of the Constants class named TRIGGER_DRIVER_ACCOUNT_TRIGGER_DEVELOPER_NAME.

The image shows the difference in the enforcing approach of the TriggerHandler framework by using the abstract keyword and the voluntary approach with the virtual keyword. The enforcing example in its entirety reads: protected abstract void beforeUpdate(); The voluntary example in its entirety reads: protected virtual void beforeInsert();

The image shows an implementation of the function to disable a specific trigger from the code by checking an additional condition in the isTriggerEnabled method, which is not present in the classic version of TriggerHandler. The additional condition is the negation of triggerDisabled.contains(triggerName parameter). triggerDisabled is a collection of data of type Set of type String with private access along with public setters for it named disableTrigger(adding to the collection) and enableTrigger(removing from the collection)

Does Singleton still make sense in Salesforce?

I will answer right away – absolutely. Singleton is a design pattern known from much older technologies than Salesforce, but it works brilliantly when programming in Apex.

Why?

  • Because it is very effective in dismissing the specter of “hitting” the limits that apply within transactions.

Singleton will work well for objects that store rarely (or not within a transaction) changing data. Examples include User, RecordType, Profile, Custom Metadata, and other custom objects that store configuration data. However, it is not worth using Singleton for Accounts, Cases, or Opps, which are the foundation of the system.

Why?

  • The Singleton pattern assumes querying data once and then returning the retrieved data without querying it again, but in some situations, we expect to fetch “fresh” data.
Benefits of Singleton

The most important benefits are the following:

  • high versatility (the design does not change for different SObjects),
  • easy to implement,
  • prevents overuse of SOQLs within a transaction.

Let’s note an important thing – in the image, a yellow frame highlights the private field of the UserService class, named currentLoggedUser, which represents the essence of the Singleton. The @TestVisible annotation has been added to the field. Its purpose is to be able to set this field to null in a situation where we want to retrieve information about the User performing a particular action within a single Unit Test within the System.runAs() method.

A classic Singleton example for a User object with a static private field currentLoggedUser and a public method getLoggedUser that, when the currentLoggedUser field is empty, retrieves user data, fills the field, and calls itself again, then returns its content if the field is not empty

The image shows a Unit Test with a quadruple call to the getLoggedUser method from the UserService class. The assertions check that the returned user Id field is equal to the Id of that currently logged-in user running the unit test, and that the number of SOQLs used in the transaction is one. The test is successful. The information about the number of SOQLs consumed comes from the getQueries() method of the Limits class

Logger framework in Salesforce projects

I’ll share a joke that I really liked: sometimes, with a few hours of debugging, we can save a few minutes of reading documentation.

Most likely, everyone needs to debug something at least once a month. Depending on the project, the process may take shorter or longer. I also assume you’re familiar with the situation when a user says the system isn’t working, but when we try to replicate the steps they took, we encounter the infamous “it works fine for me.” It may turn out that the error is impossible to reproduce without information about the exact operations, in what sequence, and with what input data the user performed. Uncertainty arises as to whether the code is definitely resilient to errors.

In such cases, Logger comes to the rescue. It allows you to get more valuable information than developer console logs without leaving debug systems permanently in the code.

There are many ideas for Logger, and each one has its own characteristics. There are ready-made packages, such as NebulaLogger, but you can also implement your own Logger. One of the examples is shown in the image below:

The image shows an icon of a notebook with a pencil and the caption Apex code that publishes a Platform Event, which triggers Flow, which inserts the record

The main benefits of using Logger

The main benefits of using Logger are as follows:

  • Controlling what data should be included in the generated system log,
  • Setting which areas of the application are subject to logging and which are not,
  • Storing logs as records with the ability to control access to them and how the log record is defined,
  • Capturing information about a user’s activities even when tracking of that user’s activities is not enabled in the developer console.

In the scheme given above, an additional advantage is the ability to log a record to the system even if we use the addError() method in the trigger context – this is due to the use of Platform Event.

The concept of Salesforce org as an airplane cockpit

While I’ve never sat behind the controls of an airplane myself, the cockpit is depicted as a panel with a large number of buttons, knobs, indicators, lights, and levers.

I think the real masterpiece is writing code in such a way that the execution of its individual parts depends on the settings in such a cockpit. A similar approach is used in TriggerHandler, where trigger control can be done through Custom Metadata or a custom configuration object.

I think it’s worth to consider such a concept. It would be nice to write a code flexible enough to allow controlling whether certain sections on a page are visible or not from the Salesforce organization level, configurable per profile, role in the system, or user’s country. This approach requires additional time at the beginning of the project to initialize the configuration object and write a Singleton for it. Also, each new functionality takes an extra few minutes to put it on/off in the control panel. However, considering the wide-ranging benefits, it’s worth investing that time. It pays off when the customer changes requirements for a particular functionality – instead of deleting the code, we modify the configuration in our “cockpit.” The ability to implement quick changes will certainly demonstrate to the client that we are prepared for any situation.

Code genericity – yes or no?

A basic rule heard in the context of coding is “Don’t repeat yourself”. That’s a fact; seeing the same code pasted multiple times in different places in the system raises questions. In such situations, it is worth using a separate method that will work with slightly different input data, for example, for different SObjects.

Saved lines of code translate into fewer lines of code to write in unit tests and savings in the number of Apex code characters used from the established limit.

 implementation of a method that is called getFieldNameToSObjects, returns Map<String, List<SObject>>, and as parameters covenants fieldNameForKey of type String and List<SObject> sobjectsToProcess. This method serves as an example of generality, by providing, for example, a List of Cases and specifying AccountId as a parameter, we receive a grouping of Cases per AccountId from it

ByPass – a rescue in difficult situations

We all encounter situations when some part of the system doesn’t work as expected. Most often, as a result, the records require a datafix after repairing the malfunctioning features. This is particularly stressful when the incident occurs in a production environment. When we use triggers, sometimes returning to the previous state of a record before the incident is difficult or even impossible (especially when some fields are not or cannot be tracked).

The last thing we need in such a situation is scheduled background mechanisms, flows, or batches to modify the record we need to analyze and create a remediation plan.

To prevent such situations, it is worth using the so-called ByPass, an additional field (checkbox/picklist) whose role is to ensure that when it’s checked, the background mechanisms do not make changes on the record, even if other input criteria are met.

Of course, the decision on how such a ByPass would work, whether it should apply only to background operations or also to triggers, and whether it should be a checkbox or a picklist, is left to the programmer’s discretion.

The image shows a field on a standard layout of any object in Salesforce. The field is called
Check before you make a SOQL – transaction limits

This approach, like Singleton, aims to mitigate the burden of Salesforce transaction limits. This simple idea is most often used in the context of a trigger. Before downloading, check if it’s even what you’re looking for.

code that collects the value of the AccountId field from the Opportunity object into the accountIds collection. The value is assigned when the StageName field is not equal to the value before the start of the transaction, and the new value of the field is contained in a collection named finalStatuses, which contains two elements. Elements: 'Closed - Lost' and 'Closed - Won’. After collecting the Id in a loop, the code checks with the IF conditional statement whether the collection is empty and, if it’s not, assigns the list of queried Accounts to a variable named accountsToCheck

Commit message – important part that allows you to track changes made to the code over time

While the question “Who did it?” is rarely or not asked at all in well-managed projects, the question “Why does something work this way and not differently?” should be asked.

Commit messages can be the key to connecting code with requirements, and the better the correlation, the easier it is to understand the cause-and-effect sequence. Therefore, in the best practices of Apex programming, it is recommended to use a clear identifier that allows the location of the requirement ticket in the code.

  • Example: If you are implementing a task with an ID of, say, 1103, it’s a good idea to make your commit message look like this: “1103 – [brief description of what the commit contains]”.
Avoid this at all costs

Finally, I’ve gathered a few more things worth avoiding when writing Apex code, which are the following:

  • hardcoding ID,
  • ignoring the existence of try-catch statements,
  • DMLs and SOQLs in loops,
  • downloading a wider dataset than needed and iterating over it instead of using the WHERE clause in a SELECT query,
  • lack of consistency in adopted design concepts,
  • incurring technical debt driven by the motive of delivering more functionality,
  • writing ineffective unit tests (covering only the code rather than checking the functionality of methods),
  • not using Data Factory in testing.
Summary

Applying good practices in writing Apex code is not just an art for art’s sake but a real solution that serves both developers and end users of the application. Implementing them often affects the system’s health, the ability to respond quickly to incidents, and overall application performance.

Moreover, when we take to heart the tips on what to avoid, we gain additional insights into how to write high-quality software in the Salesforce ecosystem.

Applying best practices really isn’t difficult. It teaches how to write code efficiently and prevents difficulties in maintaining code in the future. The decision of whether to apply them should not be up for discussion, and as part of project collaboration, we should establish which ones we adopt, and which ones do not add value when building or developing a specific solution.

Happy coding!

Author

  • Michał Łęcicki
  • Salesforce Developer
  • Salesforce Developer with over three years of experience, passionate about optimal, compliant solutions. He holds several Salesforce certifications in programming and platform configuration. He enjoys the challenge of optimizing software for performance and customer experience. Privately, a true fan of Agile methodologies, striving to implement them in everyday life, and a person who wants to achieve great things through self-development. He enjoys listening to history podcasts and delving into knowledge in the field of psychology.

Editorial study
Ernestyna Okrasa
text translation
How can you increase the chance of success of your Salesforce project?