mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 06:36:55 +01:00
Address review guidance feedback
Preserve independent event seams, cover loop-carried handled state, strengthen checkpoint and UI-handler fixtures, and align DeleteAll fallback guidance. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10646e50-2d8b-4cca-b02b-dfa78629e6a1
This commit is contained in:
parent
5f1cff2fb6
commit
ead337f9cb
11 changed files with 91 additions and 26 deletions
|
|
@ -17,6 +17,20 @@ codeunit 50241 "IsHandled Init Bad Sample"
|
|||
DiscountPct += 2;
|
||||
end;
|
||||
|
||||
procedure ApplyLineDiscounts(var SalesLine: Record "Sales Line")
|
||||
var
|
||||
LineIsHandled: Boolean;
|
||||
begin
|
||||
if SalesLine.FindSet() then
|
||||
repeat
|
||||
// Bug: the local initializes only once. A subscriber that handles
|
||||
// one line leaves true for every later iteration.
|
||||
OnBeforeApplyLineDiscount(SalesLine, LineIsHandled);
|
||||
if not LineIsHandled then
|
||||
SalesLine.Validate("Line Discount %", 5);
|
||||
until SalesLine.Next() = 0;
|
||||
end;
|
||||
|
||||
[IntegrationEvent(false, false)]
|
||||
local procedure OnBeforeApplyHeaderDiscount(var SalesHeader: Record "Sales Header"; var DiscountPct: Decimal; var IsHandled: Boolean)
|
||||
begin
|
||||
|
|
@ -26,4 +40,9 @@ codeunit 50241 "IsHandled Init Bad Sample"
|
|||
local procedure OnBeforeApplyPaymentDiscount(var SalesHeader: Record "Sales Header"; var DiscountPct: Decimal; var IsHandled: Boolean)
|
||||
begin
|
||||
end;
|
||||
|
||||
[IntegrationEvent(false, false)]
|
||||
local procedure OnBeforeApplyLineDiscount(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
|
||||
begin
|
||||
end;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,18 @@ codeunit 50240 "IsHandled Init Good Sample"
|
|||
procedure ApplyDiscounts(var SalesHeader: Record "Sales Header")
|
||||
var
|
||||
DiscountPct: Decimal;
|
||||
IsHandled: Boolean;
|
||||
HeaderIsHandled: Boolean;
|
||||
PaymentIsHandled: Boolean;
|
||||
begin
|
||||
// A freshly declared local Boolean is false.
|
||||
OnBeforeApplyHeaderDiscount(SalesHeader, DiscountPct, IsHandled);
|
||||
if IsHandled then
|
||||
exit;
|
||||
DiscountPct := 5;
|
||||
// Each fresh local is false and belongs to one non-looping raise.
|
||||
OnBeforeApplyHeaderDiscount(SalesHeader, DiscountPct, HeaderIsHandled);
|
||||
if not HeaderIsHandled then
|
||||
DiscountPct := 5;
|
||||
|
||||
// Reaching this point proves that IsHandled is still false.
|
||||
OnBeforeApplyPaymentDiscount(SalesHeader, DiscountPct, IsHandled);
|
||||
if IsHandled then
|
||||
exit;
|
||||
DiscountPct += 2;
|
||||
// Handling the header event does not suppress this independent seam.
|
||||
OnBeforeApplyPaymentDiscount(SalesHeader, DiscountPct, PaymentIsHandled);
|
||||
if not PaymentIsHandled then
|
||||
DiscountPct += 2;
|
||||
end;
|
||||
|
||||
[IntegrationEvent(false, false)]
|
||||
|
|
|
|||
|
|
@ -11,16 +11,16 @@ application-area: [all]
|
|||
|
||||
## Description
|
||||
|
||||
A routine that raises an `OnBefore…` integration event with a `var IsHandled: Boolean` parameter passes that variable by reference, so a pre-existing `true` can affect the following control flow. AL [automatically initializes Boolean variables to `false`](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-al-variables#initialization), so a freshly declared local Boolean passed to one event is already deterministic. The same is true when control flow proves the variable is `false`; for example, reaching a second raise after `if IsHandled then exit;` proves that the first raise did not leave it `true`.
|
||||
A routine that raises an `OnBefore…` integration event with a `var IsHandled: Boolean` parameter passes that variable by reference, so a pre-existing `true` can affect the following control flow. AL [automatically initializes Boolean variables to `false`](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-al-variables#initialization), so a freshly declared local Boolean passed to one event exactly once per procedure invocation is already deterministic. Initialization does not repeat for each loop iteration: a local declared outside a loop can carry `true` from one iteration to the next even when the source contains only one textual event raise. Outside a loop, reaching a later raise after `if IsHandled then exit;` also proves the value is `false`, provided that early exit is semantically correct and does not skip required downstream events.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Reset `IsHandled := false;` before a raise only when the value might otherwise carry over as `true`: the same variable is reused after an earlier raise without a control-flow proof that it is false, the value comes from an input parameter, field, or global, or earlier code seeds it. A reset on a guaranteed-false fresh local can be retained for readability, but its absence is not a correctness finding.
|
||||
Reset `IsHandled := false;` before a raise only when the value might otherwise carry over as `true`: the same variable is reused after an earlier raise without a control-flow proof that it is false, a raise is re-entered by a loop, the value comes from an input parameter, field, or global, or earlier code seeds it. Prefer separate fresh locals when independent event seams need independent handled state. A reset on a guaranteed-false fresh local used by one non-looping raise, or before a later raise reached only after a semantically valid `if IsHandled then exit;`, can be retained for readability, but its absence is not a correctness finding.
|
||||
|
||||
See sample: `initialize-ishandled-to-false-before-publishing.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Raising `OnBeforeX(…, IsHandled)` when the variable can still be `true` from an earlier raise or another source, so the new publisher call starts with stale state. Do not match a single raise using a fresh local Boolean, or a later raise reached only after `if IsHandled then exit;`.
|
||||
Raising `OnBeforeX(…, IsHandled)` when the variable can still be `true` from an earlier raise, an earlier loop iteration, or another source, so the publisher call starts with stale state. Do not match a single non-looping raise using a fresh local Boolean, or a later raise reached only after a semantically valid `if IsHandled then exit;` proves the value is false.
|
||||
|
||||
See sample: `initialize-ishandled-to-false-before-publishing.bad.al`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue