Function choice is rarely binary. Most non-trivial logic uses a mix of formula functions for inline computation, Apex methods for orchestration, and SOQL aggregates for summarization. Walk the decision tree.
- Identify the trigger
Is the logic running on record load (formula), on save (validation rule, flow, Apex trigger), inside a query (SOQL function), or on demand from a UI (Apex controller, LWC method)?
- Pick the surface
Inline read-only math: formula function. DML side effects: flow or Apex. Cross-record summary: SOQL aggregate or rollup-summary field. External call: Apex with a Named Credential or a flow with an HTTP Callout action.
- Write the smallest version first
Build the minimum function that produces the right output for one record. Skip premature abstraction.
- Test with edge cases
Null inputs, max-length text, currency conversion, multi-currency rounding. Each surface has its own failure modes; cover the ones that fit.
- Refactor once duplication appears
Wait for the third copy before factoring out. Two copies is coincidence; three is a pattern.
- Formula functions look harmless but they re-execute on every record view. Heavy VLOOKUP usage on a frequently-viewed object can quietly degrade list-view performance.
- Apex method signatures are part of the org's API surface once a managed package ships them. Renaming or removing a public method breaks every dependent consumer.
- SOQL aggregate queries return AggregateResult, not the source sObject. The Apex code calling the aggregate must handle the typeless map keys (get('expr0')) or use FIELDS() with proper aliases.
- Salesforce Functions is retired. Any documentation, blog post, or training course referencing the product is out of date. Migrate to Apex or Heroku.