Apex Methods and Structure
Method Visibility and Structure
Method Visibility Guidelines
- Public: Methods that are part of the class API, called from outside the class
- Private: Internal implementation methods, not called from outside
- Protected: Methods that may be overridden by subclasses
Default: Choose the most restrictive visibility that meets requirements. For utility classes with static methods, public is typically appropriate.
Static vs Instance Methods
Guidelines: Case-by-case based on class purpose
- Static: For stateless utility methods, factory methods, helper functions
- Instance: For stateful operations, when object state is needed
Example:
// Static utility method
public static List<Account> getAccounts() {
return [SELECT Id, Name FROM Account];
}
// Instance method (uses instance state)
public class ACME_AccountProcessor {
private String status;
public void processAccounts() {
// Uses this.status
}
}
Return Statement Patterns
Optimize for readability and shallow nesting, not for counting return statements.
Guidelines:
- Use early returns for guard clauses - Validation, permissions, and invalid or empty input should fail fast at the top. This is the default pattern for service and controller-style methods.
- Multiple exits are acceptable when each return is easy to see and the method remains short enough to follow. If exits multiply or the method grows long, extract private helpers rather than forcing a single exit.
- Prefer one return at the end when several branches all contribute to one final value or outcome and early returns would duplicate setup or obscure how the result is built. This is optional, not a general rule for "complex" methods.
Example: Early returns (guard clauses)
public static ACME_Response processRecord(Id recordId) {
if (recordId == null) {
return createErrorResponse('Record ID is required');
}
if (!hasAccess(recordId)) {
return createErrorResponse('Access denied');
}
// Main logic with less nesting
ACME_Response response = new ACME_Response();
// Process record
return response;
}
Example: Single return (intertwined branching)
Use one exit at the end when nested conditions all assign into the same outcome variable:
public static String calculateStatus(Account acc) {
String status;
if (acc.Amount > 10000) {
if (acc.Industry == 'Technology') {
status = 'Premium';
} else {
status = 'High Value';
}
} else {
status = 'Standard';
}
return status;
}
@TestVisible Annotation
Usage: Only on private methods for 1:1 testing standard
The @TestVisible annotation allows test classes to access private methods. This should only be used when necessary to achieve 1:1 test method to code method relationship.
Example:
public class ACME_AccountSelector {
@TestVisible
private String buildFieldList(Set<String> fields) {
// Private method accessible to tests
}
}
Frontend Integration (LWC/Aura)
@AuraEnabled Method Patterns
- Use
@AuraEnabledfor methods called from LWC/Aura - Use
@AuraEnabled(cacheable=true)for read-only methods that can be cached - Always return
ACME_Responseor subclass - Handle errors gracefully and return error information in response
Example:
@AuraEnabled(cacheable=true)
public static ACME_Response getAccounts() {
ACME_Response response = new ACME_Response();
try {
List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 100];
response.results.addAll(accounts);
} catch (Exception e) {
response.success = false;
response.messages.add('Error retrieving accounts');
}
return response;
}
Response Wrapper Pattern
All methods returning to frontend (LWC/Aura) must return ACME_Response or a subclass.
The ACME_Response base class provides a standardized response structure:
public virtual with sharing class ACME_Response {
@AuraEnabled
public Boolean success { get; set; }
@AuraEnabled
public List<SObject> results { get; set; }
@AuraEnabled
public List<String> messages { get; set; }
@AuraEnabled
public Exception error { get; set; }
public ACME_Response() {
success = true;
messages = new List<String>();
results = new List<SObject>();
}
}
Using Response Wrapper
Example:
@AuraEnabled
public static ACME_Response shareRecord(Id parentId, Id userId) {
ACME_Response response = new ACME_Response();
try {
// Process sharing logic
response.messages.add('Record shared successfully');
} catch (Exception e) {
System.debug('Error creating share record: ' + e.getMessage());
response.success = false;
response.messages.add(e.getMessage());
}
return response;
}
Custom Response Classes
You can extend ACME_Response for specific use cases:
public class ACME_ListResultsResponse extends ACME_Response {
@AuraEnabled
public Integer totalResults { get; set; }
public ACME_ListResultsResponse() {
super();
totalResults = 0;
}
}
Flow Integration
@InvocableMethod Patterns for Flow
- Use
@InvocableMethodfor methods called from Flow - Return appropriate data types for Flow consumption
- Handle errors appropriately
Example:
public class ACME_FlowUtilities {
@InvocableMethod(label='Process Accounts' description='Processes a list of accounts')
public static List<String> processAccounts(List<Id> accountIds) {
List<String> results = new List<String>();
// Process accounts
return results;
}
}
Change History
Version 1.0 - 2026-03-26
- Added initial version history tracking for this document.
Last Updated: 2026-03-26 Version: 1.0