
Mastering Modularity: A Beginner’s Guide to Structured Coding in Apex

When I first started coding in Apex for Salesforce, I quickly realized the importance of organizing my code into modules. It was a game-changer for me in terms of maintaining and reusing code. Let me share some insights and examples to help you grasp this concept.
Example Code Snippets and Explanation
In Apex, we can use classes to create modules. Let’s say we have a module for handling account-related operations. We can create a class named AccountService
that encapsulates all the account-related logic.
public class AccountService {
public static List<Account> getActiveAccounts() {
return [SELECT Id, Name FROM Account WHERE IsActive__c = true];
}
public static void deactivateAccount(Id accountId) {
Account acc = new Account(Id=accountId, IsActive__c=false);
update acc;
}
}
In this example, AccountService
is our module. It contains two methods: getActiveAccounts
, which fetches active accounts, and deactivateAccount
, which deactivates a given account. By encapsulating these methods in a module, we can easily reuse and maintain them.
CRS Info Solutions offers real-time Salesforce course for beginners designed to equip learners with practical knowledge and industry skills in Salesforce. Enroll for demo today.
Best Practices for Structured Coding in Apex
Now, let’s look at an example that follows best practices. We can refactor the AccountManager
class into separate modules, each with a single responsibility:
public class AccountCreator {
public static void createAccount(String name) {
// Code to create an account
}
}
public class AccountDeleter {
public static void deleteAccount(Id accountId) {
// Code to delete an account
}
}
public class AccountReporter {
public static List<Account> generateAccountReport() {
// Code to generate a report
}
}
In this refactored version, each class has a single responsibility, making the code more modular, easier to maintain, and easier to test. Additionally, by using clear and descriptive names for the classes and methods, the code becomes more readable and understandable.
1. Follow Apex Coding Standards
Explanation: Adhering to established coding standards ensures consistency and readability across the codebase. Salesforce provides a set of coding guidelines that help maintain code quality, such as naming conventions, indentation, and code organization. By following these standards, you make it easier for other developers to understand and maintain your code.
Code Example:
// Correctly following naming conventions
public class AccountManager {
public void updateAccount(Account acc) {
// method implementation
}
}
Description: In this example, AccountManager
and updateAccount
are named using clear and descriptive conventions. AccountManager
indicates that the class is responsible for managing Account
objects, and updateAccount
describes the action the method performs. This clarity makes the code more understandable and maintainable.
2. Use Descriptive Names for Variables and Methods
Explanation: Descriptive names for variables and methods improve code readability and maintainability. Choose names that clearly describe the purpose and function of the variable or method. Avoid ambiguous names and abbreviations that might confuse other developers.
Code Example:
// Using descriptive names
public class OpportunityProcessor {
public void processOpportunity(Opportunity opp) {
// process the opportunity
}
}
Description: The class OpportunityProcessor
and method processOpportunity
use descriptive names that clearly indicate their roles. OpportunityProcessor
suggests that the class handles Opportunity
objects, and processOpportunity
specifies that the method processes an Opportunity
. This approach helps developers quickly understand the purpose of the code.
3. Implement Error Handling and Logging
Explanation: Error handling and logging are critical for diagnosing issues and ensuring that your code behaves predictably. Use try-catch blocks to handle exceptions and add meaningful log statements to track the execution flow and capture error details.
Code Example:
public class OrderService {
public void createOrder(Order order) {
try {
// Code to create an order
} catch (Exception e) {
System.debug('Error creating order: ' + e.getMessage());
}
}
}
Description: In this code, the createOrder
method includes a try-catch block to handle exceptions that may occur during order creation. If an exception is caught, an error message is logged using System.debug
. This helps developers identify and diagnose issues during runtime, improving code reliability.
4. Write Reusable and Modular Code
Explanation: Creating reusable and modular code helps avoid duplication and makes maintenance easier. Break down your code into smaller, manageable methods or classes that can be reused across different parts of your application. This practice improves code organization and reduces the likelihood of errors.
Code Example:
public class Utility {
public static String formatDate(Date date) {
return date.format('MM/dd/yyyy');
}
}
public class InvoiceProcessor {
public void processInvoice(Invoice inv) {
String formattedDate = Utility.formatDate(inv.invoiceDate);
// Use formattedDate
}
}
Description: In this example, the Utility
class provides a static method formatDate
that formats a Date
object. The InvoiceProcessor
class uses this utility method to format dates. By separating date formatting into a reusable Utility
class, the code becomes more modular and easier to maintain.
5. Optimize for Bulk Processing
Explanation: Apex code should be optimized for bulk processing to handle large volumes of data efficiently. Avoid using SOQL or DML operations inside loops, and instead use collections to batch these operations. This practice prevents governor limits from being exceeded and improves performance.
Code Example:
public class ContactUpdater {
public void updateContacts(List<Contact> contactsToUpdate) {
List<Contact> contactsToUpdateInBatch = new List<Contact>();
for (Contact c : contactsToUpdate) {
c.Title = 'Updated Title';
contactsToUpdateInBatch.add(c);
if (contactsToUpdateInBatch.size() == 200) {
update contactsToUpdateInBatch;
contactsToUpdateInBatch.clear();
}
}
// Update remaining records
if (!contactsToUpdateInBatch.isEmpty()) {
update contactsToUpdateInBatch;
}
}
}
Description: This code optimizes bulk processing by batching update
operations. Instead of updating each Contact
individually, the updateContacts
method collects Contact
records in a list and performs batch updates every 200 records. This approach reduces the number of DML operations and ensures that the code remains within governor limits, enhancing performance and scalability.
Remember, the key to effective code organization is to keep your modules focused, independent, and well-named. By avoiding common mistakes and adhering to best practices, you can create more maintainable and reusable Apex code.
Common Mistakes for Structured Coding in Apex
One common mistake is overloading a module with too much functionality. For instance, consider a class AccountManager
that handles account creation, deletion, and reporting:
public class AccountManager {
public static void createAccount(String name) {
// Code to create an account
}
public static void deleteAccount(Id accountId) {
// Code to delete an account
}
public static List<Account> generateAccountReport() {
// Code to generate a report
}
}
In this example, Account Manager
is overloaded with responsibilities, making it harder to maintain and understand. It’s better to split these functionalities into separate modules.
1.Overusing SOQL Queries in Loops
Meaning: Executing SOQL queries within loops can lead to inefficient code and exceed governor limits, which restrict the number of SOQL queries that can be executed in a single transaction.
Example Code:
// Incorrect Approach
for (Account acc : accounts) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}
Description: This approach executes a separate SOQL query for each Account
, which can quickly exceed the governor limit on the number of SOQL queries allowed in a transaction (100 queries). This is inefficient and can significantly degrade performance.
Solution:
// Correct Approach
Set<Id> accountIds = new Set<Id>();
for (Account acc : accounts) {
accountIds.add(acc.Id);
}
List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];
Description: Collect all Account
IDs in a set and perform a single SOQL query to fetch all related Contacts
. This approach reduces the number of database calls and adheres to governor limits.
2.Not Handling Governor Limits Properly
Meaning: Salesforce imposes governor limits to ensure fair resource usage and maintain system performance. Ignoring these limits can cause code to fail when limits are exceeded.
Example Code:
// Incorrect Approach
for (Account acc : accounts) {
insert acc; // Can exceed DML limits
}
Description: Performing DML operations within a loop can exceed the governor limit on DML statements (150 statements per transaction). This approach risks transaction failures when handling large numbers of records.
Solution:
// Correct Approach
List<Account> accountsToInsert = new List<Account>();
for (Account acc : accounts) {
accountsToInsert.add(acc);
}
if (!accountsToInsert.isEmpty()) {
insert accountsToInsert;
}
Description: By collecting all records in a list and performing a single DML operation, you stay within the governor limits and improve performance.
3.Poor Exception Handling
Meaning: Lack of exception handling can result in unhandled errors, leading to application crashes or unstable behavior, making it difficult to identify and resolve issues.
Example Code:
// Incorrect Approach
public void updateAccounts(List<Account> accounts) {
for (Account acc : accounts) {
acc.Name = 'Updated Name';
update acc; // No exception handling
}
}
Description: Without exception handling, any errors during the update
operation will not be caught, which can cause the entire transaction to fail and make debugging challenging.
Solution:
// Correct Approach
public void updateAccounts(List<Account> accounts) {
for (Account acc : accounts) {
try {
acc.Name = 'Updated Name';
update acc;
} catch (Exception e) {
System.debug('Error updating account: ' + acc.Id + ' - ' + e.getMessage());
// Handle exception (e.g., logging or custom error handling)
}
}
}
Description: Implementing try-catch blocks ensures that exceptions are caught and logged, allowing for graceful error handling and easier debugging.
4.Inefficient Use of Collections
Meaning: Inefficient use of collections, such as using lists for operations that require fast lookups, can lead to performance degradation.
Example Code:
// Incorrect Approach
List<Account> accounts = [SELECT Id FROM Account];
for (Account acc : accounts) {
if (accounts.contains(acc)) {
// Inefficient lookup
}
}
Description: Using List.contains()
for lookups is inefficient because it involves a linear search, which can become slow with large datasets.
Solution:
// Correct Approach
Set<Id> accountIds = new Set<Id>();
for (Account acc : accounts) {
accountIds.add(acc.Id);
}
// Efficient lookup
if (accountIds.contains(acc.Id)) {
// Efficient lookup
}
Description: Using a Set
for lookups provides constant-time complexity, making lookups faster and more efficient compared to lists.
5.Neglecting to Use Bulk Safe Practices
Meaning: Processing records individually rather than in bulk can lead to inefficiencies and exceed governor limits, especially in scenarios like triggers.
Example Code:
// Incorrect Approach
trigger AccountTrigger on Account (before update) {
for (Account acc : Trigger.new) {
updateAccount(acc); // Processes one record at a time
}
}
Description: This trigger processes each Account
record one by one, which can exceed governor limits and lead to performance issues when processing many records.
Solution:
// Correct Approach
trigger AccountTrigger on Account (before update) {
List<Account> accountsToUpdate = new List<Account>();
for (Account acc : Trigger.new) {
accountsToUpdate.add(acc); // Collects records in bulk
}
// Process all records in bulk
update accountsToUpdate;
}
Description: By collecting all records into a list and performing a single bulk DML operation, the code adheres to bulk-safe practices, improving scalability and performance.
Another mistake is tightly coupling code. For example, if AccountManager
directly manipulates the fields of an Account
instance from another module without using proper access methods, it creates a tight coupling between the modules, reducing their independence and reusability.
Frequently Asked Questions for Structured Coding in Apex
1. What is the importance of structured coding in Apex?
Structured coding in Apex makes your code more readable, maintainable, and scalable. By following proper coding standards, you reduce complexity, improve performance, and make future updates or changes easier to implement.
Example:
public class AccountService {
// Querying active accounts outside the loop
public static List<Account> getActiveAccounts() {
return [SELECT Id, Name FROM Account WHERE IsActive__c = TRUE];
}
// Updating accounts with bulk DML
public static void updateAccountNames(List<Account> accounts) {
for (Account acc : accounts) {
acc.Name += ' - Updated';
}
update accounts; // Bulk DML operation
}
}
Description: The code avoids SOQL in loops and performs bulk DML operations for efficiency. Modular methods improve code reusability and maintainability.
2. How can I avoid SOQL queries inside loops?
To avoid SOQL queries inside loops, query the data once and store the results in a collection (like a List
or Map
). Then, iterate over the collection.
Example:
// Bad practice: SOQL query inside loop
for (Account acc : [SELECT Id, Name FROM Account]) {
// Do something with each account
}
// Good practice: SOQL query outside the loop
List<Account> accounts = [SELECT Id, Name FROM Account];
for (Account acc : accounts) {
// Do something with each account
}
Description: In the first example, SOQL is being executed in the loop, which can lead to governor limit exceptions. In the second example, the query is executed once, and the results are processed in the loop.
3. What is the best way to handle exceptions in Apex?
Using try-catch
blocks helps you handle errors gracefully and prevents your code from failing. Custom exceptions can also be created to handle specific error cases.
Example:
// Using try-catch for exception handling
try {
// DML operation or other code that might throw an exception
update someRecords;
} catch (DmlException e) {
System.debug('DML Exception occurred: ' + e.getMessage());
// Custom error handling
}
Description: This example shows how to catch DML exceptions. If an error occurs during the update, it is caught, and you can handle it appropriately without stopping the entire transaction.
4. What are some best practices for writing efficient DML operations?
Performing DML operations in bulk using collections like List
or Set
helps in optimizing performance and avoids hitting governor limits.
Example:
// Bad practice: DML inside loop
for (Account acc : accountList) {
update acc; // DML operation inside loop
}
// Good practice: Bulk DML outside the loop
update accountList; // Single bulk DML operation
Description: In the bad practice, DML operations are inside the loop, which can quickly exhaust the allowed DML limits. The good practice shows how to perform a bulk DML operation outside the loop for better performance.
5. How do I write reusable code in Apex?
Writing modular methods that perform specific tasks improves code reuse and maintainability. Use helper classes, inheritance, and interfaces to create a structured codebase.
Example:
// Reusable method for calculating discount
public class DiscountCalculator {
public static Decimal calculateDiscount(Decimal price, Decimal percentage) {
return price * (percentage / 100);
}
}
// Using the reusable method
Decimal discountedPrice = DiscountCalculator.calculateDiscount(100, 10);
System.debug('Discounted Price: ' + discountedPrice);
Description: Here, the calculateDiscount
method is reusable and can be called from any part of the codebase. It makes the logic easier to maintain and reuse in different contexts.