Writing a single Apex class is straightforward once you know the syntax. Writing one that integrates cleanly with the rest of the codebase, tests well, and survives three years of evolution requires deliberate design. Follow the conventions: explicit sharing context, focused single-responsibility methods, separated test classes with meaningful assertions, and a clear naming pattern.
- Decide the class purpose and naming
Pick a name that describes what the class does. AccountTriggerHandler for a trigger delegate. OpportunityService for business logic on Opportunity. AccountRepository for data access. The naming convention drives the codebase organization and onboarding speed.
- Create the file with explicit access and sharing modifiers
public with sharing class AccountService for most cases. global with sharing only when the class must be visible across namespaces. Without explicit sharing declaration, the class runs without sharing in trigger contexts, which is a security risk.
- Define focused methods with single responsibilities
Each public method should do one thing. Methods longer than 50 lines or with multiple responsibilities should be refactored into smaller pieces. Private helper methods inside the class hold reusable internal logic.
- Add inline documentation for non-obvious behavior
ApexDoc comments above public methods explain parameters and return values. Inline comments mark assumptions and business rules that future readers cannot infer from the code. Skip comments that just restate what the code obviously does.
- Create the matching test class
Separate file: AccountServiceTest.cls. Use @isTest at the class level. Use @testSetup to build common data once. Write test methods for each public method, covering positive cases, negative cases, and bulk cases.
- Run tests and verify coverage
Run from Developer Console, VS Code, or SFDX. Confirm coverage exceeds the 75 percent minimum. Review each uncovered line and decide if a test is needed. Most production codebases target 85 to 90 percent.
- Deploy via change set, metadata API, or SFDX
Bundle the class and test class together. Run a quick local test cycle before deployment. Use a CI/CD pipeline for serious projects so every change exercises the full test suite.
- Monitor in production for exceptions
Build a central error logging pattern. A custom Error_Log__c object with a static logException method catches caught exceptions for debugging. The Apex Exception Email is too sparse for production troubleshooting.
private, protected, public, or global. Default is private; declare explicitly for code review clarity.
with sharing, without sharing, or inherited sharing. Must be declared explicitly to prevent unintended access escalation.
Pinned per class. Keep on a recent version to access new features and avoid deprecated behaviors.
- Classes without explicit sharing declarations default to without-sharing semantics in trigger contexts, which is a security risk. Always declare with sharing, inherited sharing, or without sharing explicitly.
- The global access modifier is irrevocable for classes inside managed packages. Use public unless the class genuinely needs cross-namespace visibility, because downgrading from global later breaks subscribers.
- Static state persists across the transaction within a single execution context but resets between transactions. Caching with static fields works but assumptions about lifetime can produce stale data bugs.
- Test classes do not see production data by default. Use @isTest(SeeAllData=true) only when truly necessary, and prefer @testSetup for building isolated test data because SeeAllData ties tests to production data shape.
- API version pinning means old classes may execute differently from new ones. Upgrade API versions periodically during planned refactoring, not in production hotfixes.