Best Practices & Common Pitfalls

42.1 Coding standards

One trigger per object, logic in handler/service classes.

Bulkify everything: collections in, no SOQL/DML in loops, maps for lookups.

Declare sharing on every class (with sharing by default, inherited sharing for reusable code, without sharing only with a documented reason).

Enforce CRUD/FLS with user mode or stripInaccessible.

Never hard-code IDs, URLs, or credentials — use custom metadata, Named Credentials, and describe calls.

Keep methods small; name classes by role (AccountService, AccountsSelector, AccountTriggerHandler, AccountServiceTest).

Use the latest API version for new classes and periodically upgrade old ones.

Handle errors deliberately; log them persistently.

Write tests that assert behavior, cover bulk and negative paths, and run in parallel.

Run static analysis in CI; review code with a checklist.

42.2 Common pitfalls

SOQL/DML inside loops Hits 100 SOQL / 150 DML limits Query once, use maps, DML once Hard-coded record IDs Break across orgs/sandboxes Query by DeveloperName, custom metadata Multiple triggers per object One trigger + handler framework Static Boolean recursion flags Skip records after the first 200

Track processed IDs or compare old/new values Ignoring FLS/CRUD Data leaks; AppExchange review failure

WITH USER_MODE, as user, stripInaccessible Dynamic SOQL with concatenated input

Bind variables, queryWithBinds, allowlists SeeAllData=true tests Fragile, environment-dependent tests Test data factories, @testSetup Tests without assertions Coverage without confidence Meaningful Assert calls Callouts after DML "Uncommitted work pending" Callout first or async

Swallowing exceptions (catch (Exception e) {}) Silent data corruption Handle, log, or rethrow Non-selective queries on large objects Timeouts, trigger failures Indexed filters, query plan Using @future for everything

No job ID, no chaining, primitive params Queueable with finalizers Heavy logic in Schedulable execute Locked class, hard to redeploy

Schedulable only starts a batch/queueable global everywhere Locks API forever in managed packages public unless cross-namespace Large view state / heap Slow pages, heap limit errors

transient, SOQL for loops, smaller queries Mixed DML in one transaction Separate via async or runAs in tests