Skip to main content
Apex, despite being a powerful language for Salesforce development, has several common anti-patterns that can lead to performance issues, governor limit violations, maintainability problems, and bugs. Here are the most important anti-patterns to avoid when writing Apex code.
Never put SOQL queries inside loops. This can quickly exceed the governor limit of 100 SOQL queries per transaction. Instead, use relationship queries to retrieve related records in a single query, or use a map-based approach to fetch all related records in one query and then process them in memory.
Avoid performing DML operations (insert, update, delete, upsert) inside loops. This can quickly exceed the governor limit of 150 DML operations per transaction. Instead, collect all records that need to be modified and perform a single bulk DML operation outside the loop.
Always bulkify your triggers to handle multiple records efficiently. Salesforce can process up to 200 records in a single transaction, and your triggers should be designed to handle this scenario without exceeding governor limits. Use bulk queries and DML operations, and avoid processing records one at a time.
Never hardcode record IDs in your code. IDs can be different across environments (sandbox, production), and hardcoded IDs make your code environment-specific and brittle. Instead, query for the IDs dynamically or store them in custom metadata types or custom settings.
Use the Limits class to check governor limits at runtime and handle situations where you might exceed them. This is especially important for code that processes variable amounts of data or is called from multiple contexts.
Use try-catch blocks to handle exceptions gracefully. This allows you to provide better error messages, perform cleanup operations, and prevent partial transactions. Create custom exception classes for specific error scenarios to make error handling more structured.
Don’t rely on System.debug for production logging. Debug logs are not easily accessible in production and are only retained for a limited time. Instead, use a custom logging framework, custom objects to store logs, or Platform Events for more robust logging in production environments.
Always use Test.startTest() and Test.stopTest() in your test methods. These methods reset governor limits for the code executed between them and force any asynchronous processes to complete. This ensures that your tests accurately verify the behavior of your code under normal governor limits.
Don’t make methods or variables public just for testing. This exposes implementation details and breaks encapsulation. Instead, use the @TestVisible annotation to make private members accessible to test classes while keeping them private to the rest of your code.
Use Schema.SObjectType and Schema.SObjectField for dynamic references to objects and fields instead of string literals. This prevents SOQL injection vulnerabilities and catches errors at compile time rather than runtime.
Enforce object and field level security in your SOQL queries using WITH SECURITY_ENFORCED or Security.stripInaccessible(). This ensures that users can only access data they have permission to see, following the principle of least privilege.
Always explicitly declare the sharing model for your Apex classes using with sharing, without sharing, or inherited sharing. The with sharing keyword enforces record-level security, while without sharing bypasses it. Use without sharing only when absolutely necessary, and document why it’s needed.
Use a trigger framework to organize your trigger logic. A good framework separates trigger dispatching from business logic, prevents recursive trigger execution, and makes it easier to maintain and test your code. Avoid putting business logic directly in trigger files.
Follow proper naming conventions for classes, methods, and variables. Use descriptive names that clearly indicate the purpose of each entity. This makes your code more readable and maintainable. Class names should be nouns, method names should be verbs, and variable names should be descriptive.
Use asynchronous Apex (Queueable, Batch, Scheduled, or Future methods) for operations that process large amounts of data or might exceed governor limits. Asynchronous Apex has higher governor limits and can be chained or scheduled to process data in manageable chunks.