mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 22:56:55 +01:00
knowledge(error-handling): a bare [TryFunction] call propagates its error
The review agent repeatedly flagged bare calls to [TryFunction] procedures (e.g. the System Application "Xml Validation" Try* APIs) as defects, including claims that the failure is "silently swallowed". A bare call is an ordinary call: the error propagates as usual. - Add negative knowledge bare-tryfunction-call-propagates-errors.md. - Narrow ignored-tryfunction-return-disables-try-semantics to code that visibly expects the failure to be caught; the bad sample now shows that in code, and the good sample includes an intentional bare call as the clean control. - Qualify "TryFunction catches all errors" in the events article and the "must be consumed" wording in the performance article. - Error-handling leaf worklists both articles for bare [TryFunction] calls and requires the same evidence before flagging. - Add the three paired articles to the evaluation overrides.
This commit is contained in:
parent
ac249ba4c9
commit
da5a28819e
8 changed files with 71 additions and 17 deletions
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: error-handling
|
||||
keywords: [tryfunction, try-method, bare-call, error-propagation, swallowed-error, try-prefix, false-positive]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# A bare call to a [TryFunction] propagates its error; it is not a swallowed failure
|
||||
|
||||
## Description
|
||||
|
||||
A bare call to a `[TryFunction]` procedure propagates errors like any ordinary method call. When the caller ignores the Boolean result, the platform does not treat the invocation as a try-method call: an error raised inside it stops the caller exactly as an unattributed procedure would, and nothing is caught or converted to `false`. Calling a try-API this way — for example the System Application `Xml Validation` procedures `TrySetValidatedDocument`, `TryAddValidationSchema`, and `TryValidateAgainstSchema` — is therefore a legitimate way to let a validation error reach the user. Reviewers who know only that try methods "catch errors" misread such a call as a silent failure.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Treat a bare call to a `[TryFunction]` as a throwing call. Do not report that its failure is swallowed, ignored, or invisible to the caller, and do not ask the author to capture the result only to re-raise it: when the error should propagate, the bare call already does so and keeps the original error text, code, and call stack. Recommend consuming the result only when the surrounding code visibly expects to continue past or handle the failure; that case is owned by `microsoft/knowledge/error-handling/ignored-tryfunction-return-disables-try-semantics.md`.
|
||||
|
||||
A `Try` name prefix is a convention, not a semantic. Resolve the called procedure's declaration before reasoning about its error behaviour. A `Try`-named procedure without `[TryFunction]` is an ordinary Boolean method; whether ignoring its result loses a failure depends on its body, not on its name.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Review findings that flag a bare `[TryFunction]` call whose surrounding code and documentation intend the error to propagate, for example: "the Try-prefixed calls ignore their Boolean return value, so a parse or validation failure is silently swallowed", "the caller can never learn whether validation succeeded", or "the try semantics never activate, so errors escape as ordinary exceptions". The first two claims are false; the third describes the intended behaviour, not a defect.
|
||||
|
||||
Recommending `if not Try...() then Error(GetLastErrorText())` as the fix is part of the same false positive. It adds code, replaces the original error with a re-raised copy, and can move unsanitized customer content into the error message (see `microsoft/knowledge/privacy/getlasterrortext-customer-content-in-errors.md`).
|
||||
|
||||
## References
|
||||
|
||||
- [Handling errors using try methods](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-handling-errors-using-try-methods): "If a try method call doesn't use the return value, the try method operates like an ordinary method, and errors are exposed as usual."
|
||||
- [System Application `Xml Validation` codeunit](https://github.com/microsoft/BCApps/blob/main/src/System%20Application/App/XML%20Validation/src/XmlValidation.Codeunit.al): every `Try*` procedure is declared `[TryFunction]`.
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
codeunit 50301 "Try Return Bad"
|
||||
{
|
||||
procedure ImportDocument()
|
||||
procedure ImportDocument(): Boolean
|
||||
begin
|
||||
// Ignoring the Boolean result makes this an ordinary, throwing call.
|
||||
// The bare call is not a try-method call: the error stops this procedure here,
|
||||
// so the check below never sees it.
|
||||
TryImportDocument();
|
||||
if GetLastErrorText() <> '' then
|
||||
exit(false);
|
||||
exit(true);
|
||||
end;
|
||||
|
||||
[TryFunction]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
codeunit 50300 "Try Return Good"
|
||||
{
|
||||
procedure ImportDocument()
|
||||
procedure ImportDocument(): Boolean
|
||||
begin
|
||||
// The caller continues on failure, so the result is consumed.
|
||||
if not TryImportDocument() then
|
||||
Error(ImportFailedErr);
|
||||
exit(false);
|
||||
exit(true);
|
||||
end;
|
||||
|
||||
procedure ImportRequiredDocument()
|
||||
begin
|
||||
// The error should reach the user, so a bare call is correct.
|
||||
TryImportDocument();
|
||||
end;
|
||||
|
||||
[TryFunction]
|
||||
|
|
@ -13,6 +21,5 @@ codeunit 50300 "Try Return Good"
|
|||
end;
|
||||
|
||||
var
|
||||
ImportFailedErr: Label 'The document could not be imported.';
|
||||
SourceRejectedErr: Label 'The source document was rejected.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,38 @@
|
|||
---
|
||||
bc-version: [13..]
|
||||
domain: error-handling
|
||||
keywords: [tryfunction, try-method, boolean-return, ignored-return-value, error-propagation]
|
||||
keywords: [tryfunction, try-method, boolean-return, ignored-return-value, error-propagation, dead-failure-branch]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Consume a TryFunction return value to enable try semantics
|
||||
# Consume a TryFunction return value when the caller handles the failure
|
||||
|
||||
## Description
|
||||
|
||||
A procedure marked `[TryFunction]` catches errors only when the caller uses its Boolean return value. An assignment or conditional makes the invocation a try-method call; a bare call is treated as an ordinary procedure call and exposes errors as usual. The attribute alone does not make every invocation non-throwing.
|
||||
A `[TryFunction]` catches errors only when the caller uses its Boolean return value. An assignment or conditional makes the invocation a try-method call; a bare call is treated as an ordinary procedure call and exposes errors as usual. The attribute alone does not make every invocation non-throwing, so code that handles the failure of a bare call never sees that failure: the error leaves the procedure before the handling runs.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Consume the result directly: assign it to a Boolean or use the call in an `if` condition. Handle `false` immediately while the last-error state still describes that failure.
|
||||
When the caller must continue, log, count, or translate the failure, consume the result directly: assign it to a Boolean or use the call in an `if` condition. Handle `false` immediately while the last-error state still describes that failure.
|
||||
|
||||
When the error should simply propagate, a bare call is correct and needs no change; see `microsoft/knowledge/error-handling/bare-tryfunction-call-propagates-errors.md`.
|
||||
|
||||
See sample: [`ignored-tryfunction-return-disables-try-semantics.good.al`](ignored-tryfunction-return-disables-try-semantics.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Calling a `[TryFunction]` procedure as a standalone statement and assuming the attribute suppresses its errors. The call has ordinary error semantics because its Boolean result is ignored.
|
||||
Calling a `[TryFunction]` procedure as a standalone statement while the surrounding code expects the failure to be caught. Detect it from code, not from the call shape alone: the bare call is followed by a `GetLastErrorText`, `GetLastErrorCode`, or `GetLastErrorObject` check, a failure branch, failure logging, or a `false`/failure result; or it sits in a loop that is meant to continue past failed items. None of that handling sees the call's error, because the error stops the procedure first.
|
||||
|
||||
A bare call with no such handling is intended propagation, not this anti-pattern.
|
||||
|
||||
See sample: [`ignored-tryfunction-return-disables-try-semantics.bad.al`](ignored-tryfunction-return-disables-try-semantics.bad.al).
|
||||
|
||||
## See also
|
||||
|
||||
`microsoft/knowledge/performance/use-tryfunction-for-error-catching-not-rollback.md` owns transaction rollback expectations after a try method has actually caught an error.
|
||||
|
||||
## References
|
||||
|
||||
- [Handling errors using try methods](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-handling-errors-using-try-methods): "If a try method call uses the return value in an `OK:=` statement or a conditional statement such as `if-then`, errors are caught."
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ application-area: [all]
|
|||
|
||||
## Description
|
||||
|
||||
A `TryFunction` catches all errors — including errors thrown by event subscribers. When an `[IntegrationEvent]` is raised inside a `TryFunction` body, any error a subscriber raises is silently swallowed by the TryFunction's error boundary. The subscriber's logic fails, the caller sees no error, and the calling code continues as if nothing happened. Subscribers have no way to signal failure to the caller.
|
||||
A `TryFunction` whose caller consumes its Boolean result catches all errors raised during its execution — including errors thrown by event subscribers. When an `[IntegrationEvent]` is raised inside a `TryFunction` body, any error a subscriber raises is silently swallowed by the TryFunction's error boundary. The subscriber's logic fails, the caller sees no error, and the calling code continues as if nothing happened. Subscribers have no way to signal failure to the caller.
|
||||
|
||||
## Best Practice
|
||||
|
||||
|
|
|
|||
|
|
@ -29,4 +29,4 @@ See sample: [`use-tryfunction-for-error-catching-not-rollback.bad.al`](use-tryfu
|
|||
|
||||
## See also
|
||||
|
||||
`microsoft/knowledge/error-handling/ignored-tryfunction-return-disables-try-semantics.md` owns the separate call-site rule that a try method's Boolean result must be consumed.
|
||||
`microsoft/knowledge/error-handling/ignored-tryfunction-return-disables-try-semantics.md` owns the separate call-site rule that only a call that consumes the Boolean result catches errors; `microsoft/knowledge/error-handling/bare-tryfunction-call-propagates-errors.md` records that a bare call propagates them as intended.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue