mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-06 17:36:53 +01:00
Add P0 extensibility compatibility knowledge
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 645349fd-1892-48f3-8a84-db77d6abd1c3
This commit is contained in:
parent
9214f73819
commit
23af51e02d
18 changed files with 307 additions and 15 deletions
|
|
@ -0,0 +1,16 @@
|
|||
// Demonstration-only AL. Version 1 shipped with only CalculateAmount().
|
||||
interface "I Shipping Quote Bad"
|
||||
{
|
||||
procedure CalculateAmount(): Decimal;
|
||||
|
||||
// Added in version 2: every existing implementer now fails to compile.
|
||||
procedure CalculateDeliveryDate(): Date;
|
||||
}
|
||||
|
||||
codeunit 50511 "Existing Shipping Quote" implements "I Shipping Quote Bad"
|
||||
{
|
||||
procedure CalculateAmount(): Decimal
|
||||
begin
|
||||
exit(10);
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Demonstration-only AL. Interface inheritance requires runtime 14.0 / BC25.
|
||||
interface "I Shipping Quote"
|
||||
{
|
||||
procedure CalculateAmount(): Decimal;
|
||||
}
|
||||
|
||||
interface "I Shipping Quote V2" extends "I Shipping Quote"
|
||||
{
|
||||
procedure CalculateDeliveryDate(): Date;
|
||||
}
|
||||
|
||||
codeunit 50510 "Shipping Quote V2" implements "I Shipping Quote V2"
|
||||
{
|
||||
procedure CalculateAmount(): Decimal
|
||||
begin
|
||||
exit(10);
|
||||
end;
|
||||
|
||||
procedure CalculateDeliveryDate(): Date
|
||||
begin
|
||||
exit(Today() + 1);
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [16..]
|
||||
domain: interfaces
|
||||
keywords: [published-interface, interface-method, breaking-change, interface-extends, versioned-interface, appsourcecop, as0066]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Extend published interfaces; do not edit them
|
||||
|
||||
## Description
|
||||
|
||||
Adding a method to a shipped interface changes the contract every implementing codeunit must satisfy. Implementers can live in dependent extensions, so the addition breaks code the interface publisher cannot update; AppSourceCop reports AS0066. Interface inheritance is available from runtime 14.0 (Business Central 2024 release wave 2, BC25), but the original interface must remain unchanged.
|
||||
|
||||
## Best Practice
|
||||
|
||||
On BC25 or later, declare a new interface that `extends` the published interface and add the new method there. Existing implementers remain valid for the original contract, while new implementers opt in to the extended contract. For targets BC16 through BC24, where interface inheritance is unavailable, publish a new or versioned sibling interface instead.
|
||||
|
||||
See sample: `extend-published-interfaces-dont-edit-them.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Adding a procedure directly to an interface that has already shipped. Every dependent implementation must immediately add that procedure, so an otherwise compatible app update breaks its implementers.
|
||||
|
||||
See sample: `extend-published-interfaces-dont-edit-them.bad.al`.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Demonstration-only AL. A removed enum-extension value left ordinal 700 in data.
|
||||
enum 50503 "Delivery Method Bad" implements "I Delivery Method Bad"
|
||||
{
|
||||
Extensible = true;
|
||||
DefaultImplementation = "I Delivery Method Bad" = "Default Delivery Method Bad";
|
||||
|
||||
value(0; Default)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
interface "I Delivery Method Bad"
|
||||
{
|
||||
procedure Deliver();
|
||||
}
|
||||
|
||||
codeunit 50504 "Default Delivery Method Bad" implements "I Delivery Method Bad"
|
||||
{
|
||||
procedure Deliver()
|
||||
begin
|
||||
end;
|
||||
}
|
||||
|
||||
codeunit 50505 "Delivery Dispatch Bad"
|
||||
{
|
||||
procedure DeliverPersistedValue()
|
||||
var
|
||||
DeliveryMethod: Enum "Delivery Method Bad";
|
||||
Delivery: Interface "I Delivery Method Bad";
|
||||
begin
|
||||
DeliveryMethod := 700;
|
||||
// DefaultImplementation does not handle an ordinal that is not declared.
|
||||
Delivery := DeliveryMethod;
|
||||
Delivery.Deliver();
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Demonstration-only AL. UnknownValueImplementation requires runtime 7.0 / BC18.
|
||||
interface "I Delivery Method"
|
||||
{
|
||||
procedure Deliver();
|
||||
}
|
||||
|
||||
codeunit 50500 "Unknown Delivery Method" implements "I Delivery Method"
|
||||
{
|
||||
procedure Deliver()
|
||||
begin
|
||||
Error(UnknownMethodErr);
|
||||
end;
|
||||
|
||||
var
|
||||
UnknownMethodErr: Label 'The saved delivery method is no longer installed. Select another method.';
|
||||
}
|
||||
|
||||
codeunit 50501 "Default Delivery Method" implements "I Delivery Method"
|
||||
{
|
||||
procedure Deliver()
|
||||
begin
|
||||
end;
|
||||
}
|
||||
|
||||
enum 50502 "Delivery Method" implements "I Delivery Method"
|
||||
{
|
||||
Extensible = true;
|
||||
DefaultImplementation = "I Delivery Method" = "Default Delivery Method";
|
||||
UnknownValueImplementation = "I Delivery Method" = "Unknown Delivery Method";
|
||||
|
||||
value(0; Default)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [18..]
|
||||
domain: interfaces
|
||||
keywords: [unknownvalueimplementation, unknown-enum-value, persisted-ordinal, enum-extension, extension-uninstall, interface-fallback]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Handle unknown enum ordinals with UnknownValueImplementation
|
||||
|
||||
## Description
|
||||
|
||||
An enum ordinal can remain in persisted data after the enum extension that declared it is uninstalled. The ordinal is then unknown: it matches no currently declared enum value. `DefaultImplementation` does not cover this case; it covers declared values that have no explicit interface implementation. `UnknownValueImplementation`, available from runtime 7.0 (Business Central 2021 release wave 1, BC18), provides the distinct interface implementation for an unknown ordinal.
|
||||
|
||||
## Best Practice
|
||||
|
||||
On BC18 or later, set `UnknownValueImplementation = <Interface> = <Codeunit>;` on an enum that implements an interface and can be persisted. Use an implementation that reports a clear domain error or safely contains the unknown state. Keep `DefaultImplementation` separately when declared but unmapped values also need a fallback.
|
||||
|
||||
See sample: `handle-unknown-enum-ordinals-with-unknownvalueimplementation.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Defining only `DefaultImplementation` and assuming it also handles a stored ordinal whose enum value has disappeared. After an enum extension is uninstalled, converting that unknown ordinal to the interface can produce a technical runtime error instead of controlled handling.
|
||||
|
||||
See sample: `handle-unknown-enum-ordinals-with-unknownvalueimplementation.bad.al`.
|
||||
|
|
@ -11,11 +11,11 @@ application-area: [all]
|
|||
|
||||
## Description
|
||||
|
||||
An `enum` that `implements` an interface maps each value to a codeunit through the `Implementation` property. But an extensible enum can carry values that set no `Implementation` — values added later by an extension, or a value left intentionally blank. Assigning such a value to an interface variable and calling a method on it fails at runtime unless the enum provides a fallback. The enum-level `DefaultImplementation` property names the codeunit used whenever a value has no explicit `Implementation`, so resolution always yields a usable object. LLMs are generally unaware this property exists and leave the gap open.
|
||||
An `enum` that `implements` an interface maps each declared value to a codeunit through the `Implementation` property. A declared value, including one supplied by an enum extension, can omit that mapping. Assigning that value to an interface variable then fails at runtime unless the enum provides `DefaultImplementation`. This property is for declared but unmapped values; an ordinal that is no longer declared is a different case covered by `handle-unknown-enum-ordinals-with-unknownvalueimplementation`.
|
||||
|
||||
## Best Practice
|
||||
|
||||
On any extensible enum that implements an interface, set `DefaultImplementation = <Interface> = <Codeunit>;` at the enum level, pointing at a safe implementation that does nothing harmful. Values with their own `Implementation` keep using it; declared values without one resolve to the default. For an ordinal that matches no currently declared value — for example persisted data left after an enum extension is uninstalled — runtime 7.0 and later can use `UnknownValueImplementation` as a distinct fallback. Do not recommend that property to apps targeting an earlier runtime.
|
||||
On any extensible enum that implements an interface, set `DefaultImplementation = <Interface> = <Codeunit>;` at the enum level, pointing at a safe implementation. Values with their own `Implementation` keep using it; declared values without one resolve to the default. Do not rely on this property for persisted ordinals that match no declared enum value.
|
||||
|
||||
See sample: `set-defaultimplementation-on-enum.good.al`.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue