Writing Apex looks like writing Java with extra Salesforce-specific keywords. The hard part is not the syntax but the discipline: bulkification, governor limits, test coverage, and sharing context. Build in a scratch org or sandbox, test thoroughly with bulk data, and only then deploy to production with a change set or SFDX pipeline.
- Decide between trigger, class, batch, or queueable
Pick the execution context that fits the use case. Trigger for save-event reactions. Class for reusable logic invoked from elsewhere. Batch Apex for nightly processing of large data sets. Queueable Apex for chained async work. Scheduled Apex for cron-based execution.
- Open the Developer Console or VS Code with Salesforce Extensions
Setup > Developer Console for ad-hoc class authoring. VS Code with the Salesforce Extensions Pack for serious project work. SFDX commands authenticate, deploy, and run tests from the command line. Pick the toolchain that matches your team workflow.
- Create the class file with explicit sharing context
Use the with sharing keyword by default. Explicit declaration documents intent and passes Security Review checks. Inherited sharing is acceptable for utility classes; without sharing should be the exception with a comment explaining why.
- Write the logic with bulkification in mind
Process collections, not single records. Do SOQL queries outside loops. Do DML outside loops. Aggregate updates into one DML statement per object type. Every Apex review starts with checking these patterns.
- Write the test class with @isTest annotation
Create a separate test class (or use @isTest at the method level). Include positive cases, negative cases, bulk cases (200 records), and boundary conditions. Use System.runAs to test sharing behavior with different user contexts.
- Run the tests and confirm coverage
Run tests through Developer Console, VS Code, or SFDX. Confirm code coverage meets the 75 percent minimum (aim for 90 percent in production code). Review the coverage line-by-line, not just the percentage, to catch logic paths that tests skipped.
- Deploy via change set, metadata API, or SFDX
Change sets work for ad-hoc deployment but slow down at scale. Metadata API and SFDX pipelines are the standard for serious team workflows. Use a CI/CD pipeline that runs tests on every deployment, fails on coverage drops, and gates production releases.
- Monitor the class in production
Setup > Apex Jobs shows recent execution history. Setup > Email Logs shows Apex exception emails. Build a custom Error Log object and a Flow that logs caught exceptions, because the Apex Exception Email rarely lands with enough detail to debug from.
with sharing, without sharing, or inherited sharing. Explicit declaration documents intent and prevents accidental access escalation.
Controls which platform features and behaviors apply. Keep on a recent version to access new features and avoid deprecation.
Minimum 75 percent across the codebase to deploy. Aim for 90 percent in production code with meaningful assertions, not coverage padding.
- Governor limits cap every Apex execution. SOQL inside loops, DML inside loops, and unbulkified triggers are the leading causes of LimitException in production.
- Test coverage minimum is 75 percent, but coverage is not correctness. Tests with no assertions count toward coverage but verify nothing. Insist on meaningful assertions in code review.
- Apex classes without an explicit sharing declaration default to without-sharing semantics in unannotated trigger handlers. Always declare with sharing explicitly to avoid unintended access escalation.
- Recursion in triggers is a common bug pattern. A trigger that updates a record can fire itself again. Use static booleans or specialized frameworks (Trigger Handler patterns) to break the recursion.
- Old API versions accumulate behavioral debt. A class on API 30 may execute differently from a class on API 60. Periodically upgrade API versions during planned refactoring windows.