Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Mastering Apex Programming

You're reading from   Mastering Apex Programming A Salesforce developer's guide to learn advanced techniques and programming best practices for building robust and scalable enterprise-grade applications

Arrow left icon
Product type Paperback
Published in Nov 2023
Publisher Packt
ISBN-13 9781837638352
Length 394 pages
Edition 2nd Edition
Languages
Concepts
Arrow right icon
Author (1):
Arrow left icon
Paul Battisson Paul Battisson
Author Profile Icon Paul Battisson
Paul Battisson
Arrow right icon
View More author details
Toc

Table of Contents (28) Chapters Close

Preface 1. Section 1: Triggers, Testing, and Security
2. Chapter 1: Common Apex Mistakes FREE CHAPTER 3. Chapter 2: Debugging Apex 4. Chapter 3: Triggers and Managing Trigger Execution 5. Chapter 4: Exceptions and Exception Handling 6. Chapter 5: Testing Apex Code 7. Chapter 6: Secure Apex Programming 8. Section 2: Asynchronous Apex
9. Chapter 7: Utilizing Future Methods 10. Chapter 8: Working with Batch Apex 11. Chapter 9: Working with Queueable Apex 12. Chapter 10: Scheduling Apex Jobs 13. Section 3: Integrations
14. Chapter 11: Integrating with Salesforce 15. Chapter 12: Using Platform Events 16. Chapter 13: Apex and Flow 17. Chapter 14: Apex REST and Custom Web Services 18. Chapter 15: Outbound Integrations – REST 19. Chapter 16: Outbound Integrations – SOAP 20. Chapter 17: DataWeave in Apex 21. Section 4: Apex Performance
22. Chapter 18: Performance and the Salesforce Governor Limits 23. Chapter 19: Performance Profiling 24. Chapter 20: Improving Apex Performance 25. Chapter 21: Performance and Application Architectures 26. Index 27. Other Books You May Enjoy

Hardcoding

The final common mistake I want to discuss here is that of hardcoding within Apex—particularly, hardcoding any type of unique identifier such as an ID or a name. For IDs, this mistake is probably quite obvious for most developers as it is well established that between different environments, including sandbox and production environments, IDs can—and should—differ. If you are creating a sandbox from a production environment, then at the time of creation, the IDs are synchronized for any data that is copied down to the sandbox environment. Following this, IDs do not remain synchronized between the two and are generated when a record is created within that environment.

Despite this, many developers, particularly those working within a single environment such as consultants or in-house developers, will hardcode certain IDs if needed. For example, consider the following code:

for(Account acc : Trigger.new) {
    if(acc.OwnerId = 'SOME_USER_ID') {
        break;
    }
    //do something otherwise
}

This code is designed to skip updates on Account records within our trigger context owned by a particular user, most commonly an application programming interface (API) user or an integration user. This pattern enables a developer to filter these records out so that if an update is coming via an integration, actions are skipped and the integration can update records unimpeded.

Should this user ID change, then we will get an error or issue here, so it is wise to remove this hardcoded value. Given that the ID for the user should be copied from production to any sandboxes, you may ask why this is needed. Firstly, there is no guarantee that the user will not be changed for the integration, going forward. Secondly, for development purposes, when initially writing this code, the user will not likely exist in the production org, and so, in your first deployment, you will have to tweak the code to be environment specific. Thirdly, this also limits your ability to test the code effectively, going forward. We will see how shortly.

As an update to this code, some may recommend making the following change (note that this is precisely the instance where we would extract this query to a utility class; however, we are inlining the query here for ease of display and reading):

for(Account acc : Trigger.new) {
    User apiUser = [SELECT Id FROM User WHERE Username = 'my.api@user.com'];
    if(acc.OwnerId == apiuser.Id) {
        break;
    }
    //do something otherwise
}

This code improves upon our previous code in that we are no longer hardcoding the user record ID, although we are still hardcoding the name. Again, should this change over time, then we would still have an issue, such as when we are working in a sandbox environment and the sandbox name is appended to the username. In this instance, the code would not be executable, including in a test context, without updating the record to be correct. This could be done manually every time a new sandbox is created or through the code in the test. This means that our test is now bound to this user, which is not a good practice for tests, should the user change again.

In this instance, we should remove the name string to a custom setting for flexibility and improved testability. If we were to define a custom setting that held the value (for example, Integration_Settings__c), then we could easily retrieve the custom setting and the username for a query at runtime without the query, as follows:

for(Account acc : Trigger.new) {
    Integration_Settings__c setting = Integration_Settings__c.
      getInstance();
    List<User> apiUsers = [SELECT Id FROM User WHERE Username = 
      :setting.Api_Username__c LIMIT 1];
    if(acc.OwnerId == apiUsers[0]?.Id) {
        break;
    }
    //do something otherwise
}

Such a pattern allows us many benefits, as follows:

  • Firstly, we can now apply different settings across the org and profiles, should we so desire. This can be increasingly useful in orgs where multiple business units (BUs) operate.
  • Secondly, for testing, we can create a user within our test data and assign their username to the setting for the test within Apex. This allows our tests to run independently of the actual org configuration and makes processes such as continuous integration (CI) simpler.
  • We have utilized our safe navigation operator and retrieved a list of users (limiting to a size of one) to ensure that if no such user is found, we avoid any other exceptions such as our NullPointerException instance, as discussed previously.

There is, however, another fundamental fix we should make on our code—have you spotted it?

We should not be doing a query in a loop! Congratulations to the eagle-eyed readers who have been paying attention throughout and spotted the issue already. It is important when looking at some code that we do not overly focus on a single issue and fail to spot another one that may be far more troublesome. Our final improved code would, then, look like this:

Integration_Settings__c setting = Integration_Settings__c.
  getInstance();
List<User> apiUsers = [SELECT Id FROM User WHERE Username = :setting.Api_Username__c LIMIT 1];
for(Account acc : Trigger.new) {
    if(acc.OwnerId == apiUsers[0]?.Id) {
        break;
    }
    //do something otherwise
}

Finally, we could extract both of these lines (the retrieval of the custom setting and the user query) to a utility class to abstract away, for the entire transaction to use as needed.

You have been reading a chapter from
Mastering Apex Programming - Second Edition
Published in: Nov 2023
Publisher: Packt
ISBN-13: 9781837638352
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime