Add CMFRT best practices and anti-patterns documentation for various coding standards

This commit is contained in:
BeytullahCengiz88 2026-07-05 20:10:55 +02:00
parent cfcf16a00e
commit 906ddcfd72
21 changed files with 427 additions and 5 deletions

View file

@ -0,0 +1,34 @@
codeunit 55043 "CMFRT AQ FS ProForma Meth"
{
procedure CMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line")
var
Item: Record Item;
GeneralPostingSetup: Record "General Posting Setup";
IsHandled: Boolean;
begin
OnBeforeCMFRTAQFSResolveItemToGLAcc(SalesLine, IsHandled);
if IsHandled then
exit;
// Business logic inline in the shell: this early exit
// silently skips the OnAfter event below.
if SalesLine."CMFRT AQ FS Item No." = '' then
exit;
if not Item.Get(SalesLine."CMFRT AQ FS Item No.") then
exit;
// ... more inline logic ...
OnAfterCMFRTAQFSResolveItemToGLAcc(SalesLine);
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeCMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterCMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line")
begin
end;
}

View file

@ -0,0 +1,38 @@
codeunit 55043 "CMFRT AQ FS ProForma Meth"
{
procedure CMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line")
var
IsHandled: Boolean;
begin
IsHandled := false;
OnBeforeCMFRTAQFSResolveItemToGLAcc(SalesLine, IsHandled);
if IsHandled then
exit;
DoCMFRTAQFSResolveItemToGLAcc(SalesLine);
OnAfterCMFRTAQFSResolveItemToGLAcc(SalesLine);
end;
local procedure DoCMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line")
var
Item: Record Item;
GeneralPostingSetup: Record "General Posting Setup";
ItemNotFoundErr: Label 'Item %1 was not found.', Comment = '%1 = Item No.';
begin
Item.SetLoadFields("No.", "Gen. Prod. Posting Group", Description);
if not Item.Get(SalesLine."CMFRT AQ FS Item No.") then
Error(ItemNotFoundErr, SalesLine."CMFRT AQ FS Item No.");
// ... business logic only; early exits here cannot skip OnAfter ...
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeCMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterCMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line")
begin
end;
}

View file

@ -0,0 +1,26 @@
---
bc-version: [all]
domain: events
keywords: [on-before, on-after, do-procedure, thin-shell, meth, is-handled, extraction, entry-point]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CMFRT OnBefore → Do → OnAfter procedure shape
## Description
Global procedures in CMFRT Meth codeunits are thin shells: the body consists of firing `OnBefore<Name>` with `var IsHandled: Boolean`, exiting if handled, calling a local `Do<Name>` procedure that holds all business logic, and firing `OnAfter<Name>`. Local variables that only serve the business logic (record buffers, labels, working values) live in the `Do` procedure, not in the shell. This keeps the event bracket structurally impossible to bypass — the `OnAfter` event cannot be skipped by an early `exit` inside business logic, because business logic lives one level down.
## Best Practice
Write every global Meth procedure as: reset `IsHandled`, fire `OnBefore`, `if IsHandled then exit;`, call `Do<Name>(...)`, fire `OnAfter`. When review finds a global procedure whose body mixes event calls with business logic, extract the logic into `Do<Name>` and move its private variables and labels along with it.
See sample: `cmfrt-onbefore-do-onafter.good.al`.
## Anti Pattern
A global procedure whose business logic sits inline between the `OnBefore` and `OnAfter` calls. Inline bodies grow early `exit` paths that silently skip the `OnAfter` event, and their local variables and labels accumulate at the shell level where every branch can touch them. Equally wrong: declaring the event pair but never calling the events from the procedure (dead events), which advertises an extension point that never fires.
See sample: `cmfrt-onbefore-do-onafter.bad.al`.

View file

@ -23,4 +23,6 @@ See sample: `cmfrt-onbefore-onafter-all-globals.good.al`.
Publishing a global procedure without both `OnBefore` and `OnAfter` integration events, or adding events only after a downstream extension explicitly requests an extension point. A global procedure with no event bracket is a black box: dependent extensions cannot inject logic around it without an AL override, which is a breaking pattern. Adding events later is itself a non-breaking change but causes unnecessary churn and review cycles.
The inverse is equally a violation: event declarations that nothing raises. An `[IntegrationEvent]` declared but never called from any procedure, or a wrapper procedure (for example a `[TryFunction]` insert wrapper) that no caller invokes, is dead code that advertises an extension point which never fires. Wire the event into the owning procedure or delete the declaration and its plumbing.
See sample: `cmfrt-onbefore-onafter-all-globals.bad.al`.