mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-06 17:36:53 +01:00
Add CMFRT coding standards documentation and examples
- Introduced guidelines for "one codeunit one global function" architecture to enforce single responsibility in AL code. - Added best practices and anti-patterns for adding parameters via overloads to maintain backward compatibility. - Documented the importance of never deleting members in AL and always marking them as obsolete. - Established the requirement for OnBefore and OnAfter integration events for global procedures to enhance extensibility. - Defined naming conventions for CMFRT objects, including prefixes and object ID ranges to avoid conflicts. - Implemented patterns for case statements to ensure all cases are handled, including the necessity of an else clause. - Introduced the interface injection pattern to allow pluggable operations in table-level code. - Recommended using Confirm Management for user confirmations to improve testability. - Established a three-permission set pattern for security to ensure proper access control. - Created a review skill for CMFRT AL standards to automate compliance checks against established guidelines.
This commit is contained in:
parent
4119417ce4
commit
c72c0ad685
35 changed files with 866 additions and 0 deletions
|
|
@ -0,0 +1,9 @@
|
|||
codeunit 2045700 "CMFRT TST Calculator"
|
||||
{
|
||||
// Original signature modified in place — every caller must update simultaneously.
|
||||
// Dependent extensions that were not recompiled fail at runtime.
|
||||
procedure CMFRTTSTCalculateSum(NumberOne: Decimal; NumberTwo: Decimal; Decimals: Integer): Decimal
|
||||
begin
|
||||
exit(Round(NumberOne + NumberTwo, Power(10, -Decimals)));
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
codeunit 2045700 "CMFRT TST Calculator"
|
||||
{
|
||||
// Original signature — kept unchanged and marked obsolete once callers migrate.
|
||||
[Obsolete('Use CMFRTTSTCalculateSumWithRounding instead.', 'Task-23859')]
|
||||
procedure CMFRTTSTCalculateSum(NumberOne: Decimal; NumberTwo: Decimal): Decimal
|
||||
begin
|
||||
exit(CMFRTTSTCalculateSumWithRounding(NumberOne, NumberTwo, 2));
|
||||
end;
|
||||
|
||||
// New overload with extra parameter — callers can adopt at their own pace.
|
||||
procedure CMFRTTSTCalculateSumWithRounding(NumberOne: Decimal; NumberTwo: Decimal; Decimals: Integer): Decimal
|
||||
begin
|
||||
exit(Round(NumberOne + NumberTwo, Power(10, -Decimals)));
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: breaking-changes
|
||||
keywords: [overload, parameter, signature, procedure, breaking-change, backward-compatibility, arity]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# CMFRT add parameter via overload
|
||||
|
||||
## Description
|
||||
|
||||
When a CMFRT extension procedure requires an additional parameter, the original procedure signature must not be changed. A second procedure with the same name and an extended parameter list is added alongside the original. AL resolves same-name procedures by parameter count at the call site, so both coexist without conflict. The original signature is retained indefinitely and is only marked obsolete after all known callers have migrated to the new overload.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Keep the existing procedure unchanged and add a new procedure of the same name with the extra parameter appended. The original procedure may delegate to the new overload with a sensible default value for the added parameter, or it may keep its own implementation when the semantics differ. Both forms are valid. The `ObsoleteState = Pending` marker on the original should only be added once all callers have been confirmed to use the new overload.
|
||||
|
||||
See sample: `cmfrt-add-parameter-via-overload.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Adding a parameter to an existing procedure's parameter list in place, even when the intention is to add a trailing parameter that callers can ignore. AL does not support optional parameters or default values for procedure arguments. Any in-place signature change is a breaking change: every caller must be updated simultaneously and any dependent extension that was not recompiled fails at runtime.
|
||||
|
||||
See sample: `cmfrt-add-parameter-via-overload.bad.al`.
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Field deleted outright — any upgrade codeunit or dependent extension
|
||||
// that referenced "CMFRT GD Error Path" will fail to compile.
|
||||
table 2045085 "CMFRT GD Setup"
|
||||
{
|
||||
fields
|
||||
{
|
||||
field(1; "Primary Key"; Code[10]) { DataClassification = SystemMetadata; }
|
||||
field(2; "CMFRT GD ErrorPath"; Text[500])
|
||||
{
|
||||
Caption = 'Error Path';
|
||||
DataClassification = CustomerContent;
|
||||
}
|
||||
// "CMFRT GD Error Path" (field 2045325) was deleted here — breaking change.
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Obsoleted field kept at the end of the table — never deleted.
|
||||
table 2045085 "CMFRT GD Setup"
|
||||
{
|
||||
fields
|
||||
{
|
||||
field(1; "Primary Key"; Code[10]) { DataClassification = SystemMetadata; }
|
||||
field(2; "CMFRT GD ErrorPath"; Text[500])
|
||||
{
|
||||
Caption = 'Error Path';
|
||||
DataClassification = CustomerContent;
|
||||
}
|
||||
|
||||
// Obsolete section at the end — original field retained with state = Removed.
|
||||
field(2045325; "CMFRT GD Error Path"; Text[200])
|
||||
{
|
||||
Caption = 'Error Path (Obsolete)';
|
||||
DataClassification = CustomerContent;
|
||||
ObsoleteState = Removed;
|
||||
ObsoleteReason = 'Replaced by field "CMFRT GD ErrorPath" with extended length.';
|
||||
ObsoleteTag = 'Task-29100';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: breaking-changes
|
||||
keywords: [obsolete, delete, remove, breaking-change, backward-compatibility, obsolete-state, obsolete-reason]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# CMFRT never delete — always obsolete
|
||||
|
||||
## Description
|
||||
|
||||
In a CMFRT extension the following AL members must never be physically deleted: global procedures, table fields, page fields, enum values, and entire objects. Removing any of these breaks dependent extensions and upgrade paths without a compiler warning. The required approach is to retain the member, mark it with `ObsoleteState = Pending` when deprecation begins, and promote to `ObsoleteState = Removed` in a subsequent release after dependents have migrated. All obsoleted members are placed at the end of their containing object so that active code is never mixed with retired code.
|
||||
|
||||
## Best Practice
|
||||
|
||||
When a member is no longer needed, keep it in place, add `ObsoleteState = Pending`, `ObsoleteReason = '<explanation>'`, and `ObsoleteTag = '<task-id>'`. In a later release, promote to `ObsoleteState = Removed`. For fields, add the replacement field first, then obsolete the original. For procedures, add the replacement first, then obsolete the original. Provide an upgrade codeunit procedure whenever a field rename or type change requires data migration.
|
||||
|
||||
See sample: `cmfrt-never-delete-always-obsolete.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Deleting a global procedure, table field, page field, enum value, or entire AL object from the extension source. Physical deletion produces compiler errors in every dependent extension that referenced the removed member, and for table fields it causes data loss and upgrade failures in existing customer databases.
|
||||
|
||||
See sample: `cmfrt-never-delete-always-obsolete.bad.al`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue