Advanced Apex Anti-Patterns - The Invisible Performance Killers
The Invisible Performance Killers
Three tricky architectural traps that silently drain CPU limits and crash multi-tenant transactions.
Trap 1: The `SObjectType.getRecordTypeInfosByDeveloperName()` Loop Bleed
We are taught to avoid hardcoding IDs by using Schema Describe methods. It looks clean, declarative, and completely safe. But what happens when you place describe calls inside a business logic loop?
❌ The Flawed Anti-Pattern:for (Account acc : trigger.new) {
// Hidden Danger: Re-instantiating schema maps 200+ times per batch execution context
Id corporateId = Schema.SObjectType.Account.getRecordTypeInfosByDeveloperName()
.get('Corporate_Account').getRecordTypeId();
if (acc.RecordTypeId == corporateId) { /* Process */ }
}
Cache the Record Type ID **exactly once** in a static variable outside of your collection loops, or leverage a Lazy Loading singleton pattern:
// Cache outside the loop
private static final Id CORP_RECTYPE_ID = Schema.SObjectType.Account.getRecordTypeInfosByDeveloperName()
.get('Corporate_Account').getRecordTypeId();
for (Account acc : trigger.new) {
if (acc.RecordTypeId == CORP_RECTYPE_ID) { /* Constant-time evaluation */ }
}
Trap 2: The Inner-Join SOQL Parent Subquery Heap Explosion
You need to process Accounts alongside all of their related Contacts. To be efficient with governor limits, you write a single, optimized parent-child subquery.
❌ The Flawed Anti-Pattern:// Looks like a standard loop, right?
for (Account acc : [SELECT Id, Name, (SELECT Id, Email FROM Contacts) FROM Account WHERE Rating = 'Hot']) {
for (Contact con : acc.Contacts) {
// Business logic here
}
}
When dealing with high child density, flip the query architecture to drive execution from the **child object level** up to the parent using standard dot-notation, allowing true 200-record transaction stream chunking:
// Query the child directly to guarantee seamless stream chunking
for (Contact con : [SELECT Id, Email, Account.Id, Account.Name FROM Contact WHERE Account.Rating = 'Hot']) {
// Safely processes exactly 200 contact records at a time with a flat heap footprint
}
Trap 3: Dynamic String Concatenation vs. Query Bind Variables
When designing custom filter UIs or dynamic reporting endpoints, you frequently need to construct Database queries using ad-hoc strings.
❌ The Flawed Anti-Pattern:Set<String> statusSet = new Set<String>{'Active', 'Pending'};
String query = 'SELECT Id FROM Case WHERE Status IN (';
for(String s : statusSet) {
query += '\'' + s + '\',';
}
query = query.removeEnd(',') + ')';
List<Case> caseList = Database.query(query); // Vulnerable & unoptimized
Salesforce's database engine can evaluate local code memory collections directly inside dynamic execution strings using **Apex Bind Variables**. Pass the collection variable reference explicitly:
Set<String> statusSet = new Set<String>{'Active', 'Pending'};
// Clean, secure, and utilizes pre-compiled database query execution plans
String secureQuery = 'SELECT Id FROM Case WHERE Status IN :statusSet';
List<Case> caseList = Database.query(secureQuery);
💡 Senior Architect Rules of Thumb
When designing for scale inside Salesforce, remember that local syntax parsing speed is only half the battle. True performance tuning requires protecting the platform's shared memory boundaries. Minimize deep object graphs in dynamic SOQL loops, cache your structural meta-describes, and let the runtime compiler handle data binding securely.
Comments
Post a Comment