bcquality/community/knowledge/architecture/defensive-vs-offensive-code-must-match-blast-radius.md
Michael Dieringer 17e8856b35 Add 4 community knowledge articles: posting-routine pattern, email module, Word report layout, defensive/offensive blast radius
Contributed from Curabis's own BCQuality custom layer, rewritten to the
community contribution contract (no fenced code in the .md, AL samples as
sibling .good.al/.bad.al files, under 100 lines per article).
2026-08-27 18:11:04 +02:00

2.5 KiB

bc-version domain keywords technologies countries application-area
all
architecture
defensive-programming
offensive-programming
fail-fast
blast-radius
guarded-lookup
error-handling
al
w1
all

Defensive vs. offensive code must match the blast radius of being wrong

Contributions welcome — open a PR to refine or extend this article.

Description

Whether a piece of code should guard gracefully (defensive) or fail loudly (offensive/fail-fast) depends on what happens downstream if the guarded condition is silently defaulted or skipped — it isn't a matter of habit or a blanket house style. Treating every missing value the same way, defensively or offensively, is itself the anti-pattern: uniform defensiveness hides the failures that matter most, and uniform fail-fast turns ordinary, expected absence into unnecessary crashes. Two fields can look structurally identical — both read from a related record, both potentially missing — and still deserve opposite treatment: a VAT registration number used only on a printed document is something a reviewer will likely catch before the document ships, but a VAT posting group that determines the tax applied to a posted transaction is not — once posted, it's wrong money on a ledger entry, discoverable only by someone specifically auditing VAT postings.

Best Practice

Trace what a silently-defaulted or skipped value actually reaches before choosing how to guard it. If it reaches a posted ledger amount, a tax calculation, a quantity or price actually used in a transaction, or a compliance-facing output, code offensively: let the lookup fail loud (TestField, an unguarded Get() expected to always succeed, or an explicit Error) so a human sees the problem before anything posts. If it's cosmetic, informational, or easily corrected after the fact, code defensively — guard the lookup, but choose the fallback deliberately rather than accepting whatever the datatype's zero-value default happens to be.

Anti Pattern

Guarding two fields the same way "out of habit" when they carry different blast radii — for example, defaulting both a VAT registration number and a VAT posting group to blank on a failed lookup — treats a cosmetic field and a transaction-critical one as equally safe to silently default. A reviewer can spot this by a guarded Get() feeding a value that reaches a posted amount, a tax calculation, or a compliance output, with no corresponding TestField or explicit error on the missing-value path.