| bc-version |
domain |
keywords |
technologies |
countries |
application-area |
|
|
architecture |
| defensive-programming |
| offensive-programming |
| fail-fast |
| blast-radius |
| guarded-lookup |
| error-handling |
|
|
|
|
Defensive vs. offensive code must match the blast radius of being wrong
Description
Whether a piece of code should guard gracefully (defensive) or fail loudly
(offensive/fail-fast) is not a matter of habit or a blanket house style —
it's a decision that depends on what happens downstream if the guarded
condition is silently defaulted or skipped. Treating every missing value
the same way, defensively or offensively, is itself the anti-pattern:
uniform defensiveness hides the failures that matter most; uniform
fail-fast makes 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 is sent. A VAT Prod.
Posting Group that determines the VAT % 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, and expensive to correct.
Same shape of code, opposite correct behavior.
The Decision
Trace what a silently-defaulted or skipped value actually reaches before
deciding how to guard it:
- Does it reach a posted ledger amount, a VAT/tax calculation, a
quantity or price actually used in a transaction, or a legally/
compliance-facing output (e.g. numbers submitted in a VAT
declaration)? → Code offensively. Let the lookup fail loud
(
TestField, an unguarded Get() that's expected to always succeed,
or an explicit Error) so a human sees the problem before anything
posts — a blocked task is always cheaper than a wrong ledger entry.
- Is it cosmetic, informational, or easily corrected after the fact
(a display field, an optional UI enhancement, a report that hasn't
run yet)? → Code defensively — guard the lookup, but the fallback
must be an explicit, deliberately-chosen, named business value, never
a blank string or zero that happens to be the datatype default with
no thought behind it (see ambiguous-record-failure-handling-must-be-clarified
for the general Get/Insert/Modify/Delete version of this same choice).
- Genuinely unsure which category a field falls into? That's a
clarify-before-building moment, not a coin flip — ask the
developer, don't default to whichever style is more familiar to write.
Anti Pattern
// Both fields guarded the same way, out of habit rather than analysis.
if Header.Get(DocumentNo) then
VATRegNo := Header."VAT Registration No."; // low blast radius — fine to default
// but the same pattern, unexamined, was also applied here:
if Header.Get(DocumentNo) then
VATProdPostingGroup := Header."VAT Prod. Posting Group"
else
VATProdPostingGroup := ''; // high blast radius — silently wrong VAT on posting
Best Practice
// Low blast radius: guard, with an explicit chosen fallback.
if Header.Get(DocumentNo) then
VATRegNo := Header."VAT Registration No.";
// (blank is an acceptable, deliberately-considered default here — the
// field is informational and a reviewer sees it before the document ships)
// High blast radius: let it fail loud, because this feeds posted VAT.
Header.Get(DocumentNo); // expected to always succeed for a line's own header
Header.TestField("VAT Prod. Posting Group");
VATProdPostingGroup := Header."VAT Prod. Posting Group";
Source
Michael Dieringer, 2026-08-13, refining ambiguous-record-failure-handling-must-be-clarified's
VAT-critical sharpening after noting that not all VAT-adjacent fields carry
the same risk — a VAT registration number and a VAT posting group are not
equally dangerous to get wrong, and a single blanket rule for "VAT fields"
would either over- or under-guard depending on which one it's applied to.