mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 22:56:55 +01:00
Add 18 more community AL/BC patterns across appsource, data-modeling, error-handling, security, style, testing, ui, upgrade, and web-services
Second contribution from CURABIS ApS, generalized from patterns observed across real AppSource/PTE development. Cross-checked against the current microsoft/knowledge corpus before opening; several originally-drafted candidates were dropped as duplicates of existing files.
This commit is contained in:
parent
07e324ddbc
commit
a4d85c3e9e
50 changed files with 1262 additions and 0 deletions
|
|
@ -0,0 +1,18 @@
|
|||
codeunit 50101 "Credit Memo Routing"
|
||||
{
|
||||
procedure PostSalesLine(var SalesHeader: Record "Sales Header"; var SalesLine: Record "Sales Line"; CustomerNo: Code[20]; Amount: Decimal)
|
||||
begin
|
||||
// Set the customer number
|
||||
SalesHeader.Validate("Sell-to Customer No.", CustomerNo);
|
||||
// Insert the line
|
||||
SalesLine.Insert(true);
|
||||
// Check if the amount is positive
|
||||
if Amount > 0 then
|
||||
// Post the entry
|
||||
PostEntry(Amount);
|
||||
end;
|
||||
|
||||
local procedure PostEntry(Amount: Decimal)
|
||||
begin
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
codeunit 50101 "Credit Memo Routing"
|
||||
{
|
||||
procedure PostSalesLine(var SalesHeader: Record "Sales Header"; var SalesLine: Record "Sales Line"; CustomerNo: Code[20]; Amount: Decimal)
|
||||
begin
|
||||
SalesHeader.Validate("Sell-to Customer No.", CustomerNo);
|
||||
SalesLine.Insert(true);
|
||||
|
||||
// Negative amounts arrive from credit memos routed through this
|
||||
// codeunit; PostEntry() rejects them, so they're filtered here.
|
||||
if Amount < 0 then
|
||||
exit;
|
||||
|
||||
if Amount > 0 then
|
||||
PostEntry(Amount);
|
||||
end;
|
||||
|
||||
local procedure PostEntry(Amount: Decimal)
|
||||
begin
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: style
|
||||
keywords: [comments, verbosity, self-documenting, restate, tutorial-style]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Comments must not restate what the code already shows
|
||||
|
||||
## Description
|
||||
|
||||
A comment above nearly every statement that just narrates what the statement already says (`// Validate the customer number` above `SalesHeader.Validate("Sell-to Customer No.", CustomerNo)`) adds noise without adding information. Production AL — the Base Application, mature partner codebases — is comment-sparse by comparison: identifiers do the explaining, and a comment appears only when the code alone can't carry the reason.
|
||||
|
||||
A comment earns its place only when it captures something the code cannot: a non-obvious business rule, a workaround for a specific platform limitation, or a constraint that would surprise the next reader. If removing the comment would leave the reader no worse off, the comment should not have been written.
|
||||
|
||||
This does not override required structural documentation — feature/scenario test tags and XML-doc summaries on public library procedures remain required where they apply; those are structural markers, not narrative comments.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Let the code speak for itself; reserve comments for the reason a reader could not otherwise infer.
|
||||
|
||||
See sample: `al-comments-must-not-restate-what-code-already-shows.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
A comment line before every statement, repeating in English what the statement's own identifiers already say.
|
||||
|
||||
See sample: `al-comments-must-not-restate-what-code-already-shows.bad.al`.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
page 50100 "Sales Line Card"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = "Sales Line";
|
||||
|
||||
actions
|
||||
{
|
||||
area(Processing)
|
||||
{
|
||||
action(Recalculate)
|
||||
{
|
||||
trigger OnAction()
|
||||
begin
|
||||
Rec."Total Amount" := Rec.Quantity * Rec."Unit Price";
|
||||
Rec.Modify();
|
||||
end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
codeunit 50100 "Sales Line Management"
|
||||
{
|
||||
procedure RecalculateLine(var SalesLine: Record "Sales Line")
|
||||
begin
|
||||
SalesLine."Total Amount" := SalesLine.Quantity * SalesLine."Unit Price";
|
||||
SalesLine.Modify();
|
||||
end;
|
||||
}
|
||||
|
||||
page 50100 "Sales Line Card"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = "Sales Line";
|
||||
|
||||
actions
|
||||
{
|
||||
area(Processing)
|
||||
{
|
||||
action(Recalculate)
|
||||
{
|
||||
trigger OnAction()
|
||||
begin
|
||||
SalesLineMgt.RecalculateLine(Rec);
|
||||
end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
SalesLineMgt: Codeunit "Sales Line Management";
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: style
|
||||
keywords: [pages, business-logic, codeunit, separation-of-concerns, presentation-layer]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Keep business logic out of page objects
|
||||
|
||||
## Description
|
||||
|
||||
A page procedure that calculates a value and assigns it to a field, calls `Rec.Modify()` directly, or implements a business rule is an architecture violation even when it compiles. Pages are a presentation layer: they bind data to the UI and invoke actions. Calculations, validations, and record mutations belong in codeunits, where they can be tested, reused, and called consistently regardless of which page (or API, or batch job) triggers them. When logic lives on a page, it only applies when a user opens that specific page — the same business rule silently doesn't run through any other entry point.
|
||||
|
||||
A narrow set of patterns are conventional rather than violations:
|
||||
- A setup page reading and writing its own singleton setup record.
|
||||
- A dedicated "Run Conversion" page invoking a conversion codeunit directly.
|
||||
- The standard singleton-initialization idiom on `OnOpenPage` (`if not Rec.Get() then begin Rec.Init(); Rec.Insert(); end`) used by cue/activities pages to bootstrap their own presentation-state record — this is not business logic, it is the same pattern used throughout base-app cue pages.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Delegate all business operations to a codeunit: the page owns presentation, the codeunit owns logic. A calculation or validation triggered from a page action should call a codeunit procedure rather than compute the result inline.
|
||||
|
||||
See sample: `pages-must-not-contain-business-logic.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Direct calculations in a page trigger (e.g. `Rec."Total Amount" := Rec.Quantity * Rec."Unit Price"`), calls to `Rec.Modify()` from a page trigger, or business-rule validation embedded in `OnValidate`/`OnAction` instead of routed through a codeunit.
|
||||
|
||||
See sample: `pages-must-not-contain-business-logic.bad.al`.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: style
|
||||
keywords: [folder-structure, feature-organization, source-layout, maintainability]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Organize AL source by business feature, not object type
|
||||
|
||||
## Description
|
||||
|
||||
Source folders inside an AL app should group files by the business feature or module they belong to (`src/Sales/Invoice/`, `src/NoSeries/`), not by which kind of AL object they are (`src/Tables/`, `src/Pages/`, `src/Codeunits/`). Object-type folders scatter everything belonging to one feature across half a dozen directories, so a developer picking up a feature has to jump between folders that share nothing but object type to see the whole picture. Feature folders keep a table, its pages, its codeunits, and its test setup physically together.
|
||||
|
||||
Code genuinely shared across multiple features (utility codeunits, common interfaces, shared enums) belongs in a `Common` or `Shared` folder, not duplicated per feature and not left in a catch-all root.
|
||||
|
||||
## Best Practice
|
||||
|
||||
src/
|
||||
├── NoSeries/
|
||||
├── Sales/
|
||||
│ ├── Invoice/
|
||||
│ └── Order/
|
||||
└── Common/
|
||||
|
||||
Each feature folder holds every object type it needs; shared code has one dedicated home.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
src/
|
||||
├── Tables/
|
||||
├── Pages/
|
||||
└── Codeunits/
|
||||
|
||||
Finding everything related to one feature now requires searching multiple folders and mentally reassembling it from scattered pieces.
|
||||
Loading…
Add table
Add a link
Reference in a new issue