Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
Full Apex Classes entry
How-to guide

How to write a maintainable Apex Class

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.

By Dipojjal Chakrabarti · Founder & Editor, Salesforce DictionaryLast updated May 16, 2026

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

Access Modifierremember

private, protected, public, or global. Default is private; declare explicitly for code review clarity.

Sharing Contextremember

with sharing, without sharing, or inherited sharing. Must be declared explicitly to prevent unintended access escalation.

API Versionremember

Pinned per class. Keep on a recent version to access new features and avoid deprecated behaviors.

Gotchas
  • 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.

See the full Apex Classes entry

Apex Classes includes the definition, worked example, deep dive, related terms, and a quiz.