Table of Contents

AL Coderichtlinien

Diese Seite erläutert bzw. ergänzt Richtlinien für die Programmierung von Extensions. Ganz allgemein gelten folgende Quellen als valide Regeln, sofern nicht in der Entwicklungsrichtlinie zur Entwicklung eine abweichende oder verfeinernde Regelung getroffen wird:

AL Dokumentation bei Microsoft: https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-dev-overview

Keine Überraschungen und nichts verstecken

Generell ist Code so aufzubauen, dass eine spätere Anpassung und Wartung gut möglich ist. Ein anderer Entwickler (mit Kenntnis der ggf. angepassten Standardprozesse) muss in der Lage sein, sich auch ohne spezifische Einweisung in den Code einarbeiten zu können. Das Prinzip ist: "Überrasche niemanden. Verstecke nichts."

Code muss sich immer da befinden, wo man ihn erwartet. Auch wenn man das Projekt nicht gut kennt

Keine Überraschung und damit erlaubt ist z. B.:

  • Tabelle stellt eine Klasse dar
  • Die Funktionen der Klasse sind von der Klasse aus zu finden. Bevorzugt über Funktionen der Tabelle (= Tabellenfunktion vorhanden, die dann Funktionen in Codeunits aufruft). Noch tolerabel, wenn die Funktionen in namentlich erkennbaren und in der Ordnerstruktur zuzuordnenden Orten liegen.
  • Pages haben nur Funktionen, die etwas mit der Darstellung zu tun haben. Alle Funktionen für den Datensatz liegen in der Tabelle und/oder relevanten Codeunit(s)
  • Funktionalitäten im Workspace logisch organisiert (Ordnerstruktur, Namen der Objekte, Namen der Funktionen)
  • Es werden nur dann globale Variablen verwendet, wenn es technisch nicht anders geht

Versteckt und damit verboten ist z. B.:

  • Sammelcodeunit für unzusammenhängende Funktionen, die nichts miteinander zu tun haben
  • Funktioneller Code auf Pages
  • Funktionen für eine Tabelle verstreut in Codeunits und keine Mindestvorsorge dafür, dass man sie finden kann.

Code muss sich selbst erläutern

Lesbar und damit erlaubt ist z. B.:

  • Auf der obersten Ebene werden nur Funktionen aufgerufen, die sprechende Namen haben. Es ist daher auch ohne Blick in die Tiefe erkennbar, wie der Prozess aufgebaut ist.
  • Jede Funktion erfüllt immer nur einen Zweck, und ihr Name lässt den Zweck erahnen
  • Jede Funktion erfüllt Ihren Zweck mit möglichst wenig Verzweigungen. Wenn Verzweigungen benötigt werden, dann werden Unterfunktionen implementiert. Die Funktion selber bleibt lesbar.

Nicht lesbar und damit verboten ist z. B.:

  • Man muss mehrmals am Scrollrad drehen, um die Funktion ganz zu sehen
  • Eine Funktion macht alles selber. Prüfen, Verzweigen, Abwickeln, Werte zurückgeben, Daten schreiben...
  • Eine Funktion heißt so, als würde sie etwas prüfen, aber in Wirklichkeit schreibt sie auch Daten
  • Eine Funktion heiß so, als würde Sie etwas im Verkauf tun, macht aber etwas im Einkauf

Code muss testbar sein

Wenn man seine Funktionen nicht in einem automatisierrten Test prüfen kann, dann sind sie falsch strukturiert. Es ist nie die Schuld des Test Framework, wenn man es nicht auf seine Funktionen anwenden kann.

CodeCops

Die im Workspace aktivierten CodeCops erzeugen Warnungen für eine Vielzahl an unsauberen Konstruktionen. Die Warnungen müssen berücksichtigt werden. Code muss ohne Warnungen kompilieren.

Es ist nicht gestattet, im Projekt eigenständig Warnungen zu unterdrücken, weder durch globale Einstellungen (Workspace, app.json, ruleset.json) noch durch Pragmas im Code. Ausnahmen sind nur in Abstimmung mit der Entwicklungsleitung oder einem Lead Developer möglich.

Eine Ausnahme kann bei Warnungen durch die Verwendung obsoleter Strukturen in bestimmten Fällen gemacht werden. Hier kann es übergangsweise erforderlich sein, Referenzen zu obsoleten Strukturen beizubehalten, und die dabei entstehenden Warnungen können entweder toleriert oder durch Pragmas unterdrückt werden. Dies passiert v. a. dann, wenn eine Struktur als obsolet deklariert ist, aber noch kein Ersatz verfügbar ist.

Präfix/Suffix

Um Konflikte mit mehrfach verwendeten Namen zu umgehen müssen für Objektnamen und innerhalb von Tabellen für Feldnamen spezifische Präfixe oder Suffixe vergeben werden. Für die Entwicklung von kundenindividuellen Extensions ist das Kürzel PTE zu verwenden. Auf keinen Fall darf das Kürzel GOB verwendet werden, dieses ist bei Microsoft für die Produkte der GOB (=unitop) registriert.

Technisch betrachtet ist es egal ob das Kürzel als Präfix oder Suffix verwendet wird - für die Entwicklung von Extensions in Projekten wird durchgängig ein Präfix verwendet.

Für eigene Objekte wird das Präfix auf der obersten Objektebene gesetzt. Hier ein Beispiel:

codeunit 50000 "PTE Address Format"

Für pages/tables/enums/reports/permissionsets im Business central Standard oder anderen Apps, die erweitert werden, muss das Präfix auf der obersten Objektebene und zusätzlich auch auf der Ebene von control/field/action/procedure/values/dataitem/column angegeben werden. Hier ein Beispiel:

field(50000; "PTE Bill-to Cust.-No. Member"; Code[20])
{
}
field(50001; "PTE Registered On"; Date)
{
}

Konflikte mit gleich benannten Konstrukten mit ebenfalls dem Präfix PTE sind dann entsprechend durch Umbenennung aufzulösen. Es darf nicht einfach auf ein anderes Präfix gewechselt werden und auch der Tausch von Präfix zu Suffix ist ebenfalls nicht gestattet.

Präfix/Suffix für Funktionen und andere Controls

Generell muss das Präfix PTE immer verwendet werden, wenn eine Erweiterung des Microsoft oder unitop Standard erfolgt. Dies gilt zum Beispiel für

  • global procedures in Table-, Page-, Report- und Enum-Extensions
  • Bei Zugriffsmodifikatoren in Extension Objekten, z. B. protected var
  • Neue Controls oder Control-Gruppen auf Pages und Reports

Dokumentation im Quellcode

Die erforderliche Kommentierung von Objekten erfolgt über die Verwendung von git und der jeweiligen Verbindung zu konkreten Entwicklungstätigkeiten. Darüber hinaus sorgt der Entwickler für eine sinnvolle Ordnerstruktur der Quellcodedateien sowie eine lesbare Benennung und Strukturierung im Quellcode. Darüber hinaus ist Kommentierung verboten, außer sie ist im Folgenden explizit erlaubt.

Dokumentation von Quellcode-Änderungen

Es findet keine Kommentierung von Quellcode statt der bei der Erledigung einer Entwicklungsaufgabe verändert wird. Zu ändernder Quellcode wird nicht auskommentiert, sondern überschrieben bzw. gelöscht. Die erforderliche Dokumentation dieser Tätigkeiten und auch die Sicherung des Zustandes vor und nach der Änderung erfolgt über die Verwendung von git und der jeweiligen Verbindung zu konkreten Entwicklungstätigkeiten (Commits, Pull Requests).

Dokumentation zum Verständnis von Quellcode

Kommentierung zum Verständnis von Quellcode ist nur zulässig wenn ein Codeabschnitt funktionell erläutert werden muss weil z. B. eine komplexe Kalkulation o. vgl. stattfindet. Die Kommentierung ist allerdings dennoch unzulässig, wenn sie durch eine bessere Struktur des Quellcode überflüssig sein könnte. In aller Regel ist ein Kommentar falsch der durch das Auslagern eines Codeblocks in eine sprechend benannte Funktion ersetzt werden kann.

  • Verboten: Wenn Kommentare benötigt werden um den Programmablauf an sich verstehen zu können, ist der Code schlecht strukturiert. Die Kommentierung ist damit unzulässig.
  • Erlaubt, wenn nicht durch obiges verboten: In einer Funktion, die nur genau eine Aufgabe erfüllt, und trotzdem von Erläuterung profitiert.

Dokumentation von Funktionen und Objekten

Das Kommentieren von Funktionen oder ganzen Objekten ist grundsätzlich erlaubt, wenn es in der Form von XML-Kommentaren erfolgt.

  • Erlaubt, wenn es dem Verständnis der Lösung tatsächlich hilft: In einer globalen Funktion die aus Sicht des Entwicklers einer Erklärung bedarf weil sie eine zentrale Rolle in der Lösung spielt.
  • Verboten, wenn der Name der Funktion/Objekt samt Parametern bereits alles über den Inhalt und Verwendung verrät: Eine Funktion mit dem Namen CheckSetupNoSeries() ist verständlich und bedarf keiner genaueren Erläuterung. Eine Funktion, die Teil einer Schnittstelle ist und z. B. lautet CreateCustomerFromMyWebShop(CustomerTemplateCode: Code[20]), kann hingegen von einem Kommentar profitieren, wenn gewisse Voraussetzungen gegeben sein müssen, etc.

Hier ein Beispiel aus unitop:

/// <summary>
/// Sets the Calculated Pension Result in the Pension Buffer (Sub) Header
/// </summary>
/// <param name="PensionBufferHeader">PensionBuffer Header to calculate. Temp Records not supported.</param>
procedure RunPensionCalculation(var PensionBufferHeader: Record "GOB Pension Buffer Header")

In lokalen oder internen Funktionen sind solche XML-Kommentare nicht verboten, müssen dann aber beim Review ggf. begründet werden. Im Zweifel entscheidet der Reviewer. Grundsätzlich ist von jedem Entwickler zu prüfen ob seine Namensgebung der Funktion samt Variablen auch so angepasst werden kann, dass ein Funktionskommentar überflüssig ist.

Hinweis

Es ist nicht erlaubt in einem XML-Kommentar oder Kommentar zu einem Objekt Referenzen zu Feldnamen und/oder Objekten herzustellen. Beispiel: "Die Funktion macht ... wenn im Feld "PTE My cool field" ...". Es besteht keine Möglichkeit später noch festzustellen, ob das benannte Feld nicht schon obsolet ist oder vielleicht doch anders lautet. Besser: Der Entwickler beschreibt den Prozess der mit der Funktion/Objekt unterstützt wird. Auch hier entscheidet im Zweifel der Reviewer.

Dokumentation in Unit Tests

In Unit Tests sind abweichend von funktionellem Code spezifisch Kommentare/Dokumentation zwingend erforderlich. Tests werden im Feature/Scenario/Given/When/Then-Schema kommentiert. Zu den Vorgaben für Tests siehe Test Units.

Namenskonventionen

Zur Benennung von Elementen außer Dateinamen gelten folgenden Hinweise. Zur Benennung kann im Detail immer ein gewisser Interpretationsspielraum bestehen. Benennungen die explizit nicht erlaubt sind, werden daher besonders benannt.

Allgemeine Regeln für alle Elemente:

  • Elemente werden sprechend benannt
  • Elemente sind immer Englisch benannt
  • Ungarische Notation ist verboten! (= recItem, decSomeNumber, ...). Also: kein Präfix für den Typ voranstellen
  • In den Elementen können Teile sprechend abgekürzt werden
  • Leerzeichen in Benennungen sind verboten, außer es handelt sich um Tabellenfelder.
Art Beispiele Hinweise
Variablen Boolean: IsLicensed
Integer,Decimal, etc.: QuantityOfWhatever
Record: Contact
Text, Code, etc.: SomeNameWeHandle
Keine Leerzeichen
CamelCase um Wörter zu trennen
Funktionen Prüfung mit Rückgabe boolean: IsModuleLicensed()
Funktion als Anweisung: DoPostInvoice(), CheckPreconditionsAreValid()
Analog zu den Regeln für Variablen.
Zu Ergänzen um einen Anweisungscharakter
Event Publisher OnBeforeInitSomeFunction(), OnAfterSomePointInFunction() Analog zu den Regeln für Funktionen.
Zu ergänzen um den möglichst exakten Kontext in dem das Event ausgelöst wird.
Der Name muss klar unterscheidbar von anderen Publishern im selben Kontext sein und möglichst klar erkennen lassen wo im Programmfluss das Event erhoben wird.
Event Subscriber ModifyDataOnAfterSomePointInFunction() Analog zu den Regeln für Funktionen.
Textkonstanten RecIdNotFoundErr, ExpectedMsgTxt, ... Generische Namen sind nicht erlaubt (01Err, ...).

API Objekte folgen einer einheitlichen Namenskonvention. Im Kern werden die Regeln der Microsoft-Dokumentation angewendet und dann anhand folgender Regeln verfahren:

  • Name der API Page mit API und Versionsnummer
  • APIGroup startet mit pte
  • APIPublisher ist pte
  • Es wird camelCase verwendet
  • EntityName ist Singular
  • EntitySetName ist Plural
  • Caption entspricht dem EntitySetName

Hier ein Beispiel:

page 5059545 "PTE SomeData API1.0"
{
    APIGroup = 'pteSomeEntity';
    APIPublisher = 'pte';
    APIVersion = 'v1.0';
    Caption = 'pteSomeCaption',
    Comment = 'de-DE=pteSomeCaptionOrComment';
    EntityName = 'pteSomeEntity';
    EntitySetName = 'pteSomeEntities';
}

Formatierung und Einrückung

Formatierung und Einrückung werden über AutoFormat (Shift+Alt+F) erreicht. Individuelle Formatierungsentscheidungen sind nur dann erlaubt, wenn AutoFormat sie so bestehen lässt und der Reviewer einverstanden ist.

Application Area und Usage Category

Alle Elemente die in der GUI von Business Central sichtbar werden benötigen das Property Application Area. Das betrifft damit Pages und Reports. Die Application Area muss gesetzt sein damit betroffene Elemente sichtbar werden.

ApplicationArea = All;

Für die überwiegende Menge der Funktionen in Extensions kann die obige Ausprägung verwendet werden. Lediglich bei Elementen die im Bereich Service und Produktion ergänzt werden kann fallweise entschieden werden z. B. Suite als Application Area zu verwenden.

Das Property Usage Category sorgt dafür das Pages und Reports über die Suche auffindbar werden. Das Property muss daher je Page und Report gesetzt sein sofern der Anwender das Objekt über die Suche auffinden und nutzen können soll.

Data Classification

Tabellen und Tabellenfelder (sowohl originär als auch in Table Extensions) benötigen das Property Data Classification. Das Property muss zum einen vorhanden sein und zum anderen mit einem konkreten Wert belegt sein. ToBeClassified ist unzulässig.

Die Klassifizierung, die für Extensions programmatisch festgelegt wird, wird für den Anwender zum Vorschlag für sein DSGVO Reporting. Dementsprechend setzen wir Werte ein die dem Sinn einer Tabelle bzw. v. a. der einzelnen Felder am nächsten kommen. Siehe auch die Dokumentation von Microsoft zu DataClassification Property.

Permission Sets

Für jede Extension muss mindestens ein Permission Set mitgeliefert werden. Es ist unzulässig, eine Extension ohne Permission Sets freizugeben. Es werden das Permission Set Object verwendet. Es werden alle Objekttypen berücksichtigt, nicht nur Tabellendaten.

Umgang mit .NET-Interop und Zugriff auf interne Strukturen

Aufgrund der Verschärfung der Lizenzbestimmungen seitens Microsoft für AL Code, der nicht cloud-konform ist, steht dies generell nicht mehr zur Verfügung und darf auch normalerweise nicht wieder verfügbar gemacht werden. Siehe Struktur einer Extension.

Nummerierung

Objekte und Tabellenfelder werden in AL weiterhin nummeriert. Extensions werden im individuellen Nummernkreis des Kunden entwickelt. Dies wird on-premises durch die Lizenz des Kunden definiert und entsprechend verwendet. In SaaS-Projekten steht technisch der gesamte Individualkreis zur Verfügung. Eine Eingrenzung aus organisatorischen Gründen ist empfohlen aber nicht vorgeschrieben.

Sprache und Übersetzung

Wir passen uns dem Umfeld von Business Central an: Es wird auf Englisch entwickelt. Deutsche Bezeichnungen für Entitäten im Code sind unzulässig. Deutsche Kommentare im Code sind unzulässig.

Azure DevOps Work Items dürfen Deutsche Titel und Inhalte haben, sofern die Vereinbarungen mit Kunden oder Partnern dem nicht widersprechen.

Es ist gestattet, bei einem Projekt für einen Kunden, der nur Deutsch als Anwendungssprache verwendet, nur eine Deutsche Übersetzungsdatei mit auszuliefern. Dies gilt sofern im Projekt nichts anderslautendes dazu vereinbart wurde. Die technischen Bezeichnungen (z. B. Feldnamen, Variablennamen) müssen weiterhin Englisch sein.

Option vs. Enum

Anstelle des Datentyps Option muss der neue Objekttyp Enum verwendet werden.

Event Subscriber

Keine unnötige Ausführung von Subscriber-Code

In Subscribern muss immer früh geprüft werden ob das gewünschte Szenario auch tatsächlich gerade abläuft und ob die Bedingungen erfüllt sind unter denen der Code wirken soll. Falls nein muss ein möglichst frühes exit() erfolgen.

Dazu gehört ebenfalls die Prüfung auf potentiell temporäre Daten. Anbei ein Beispiel aus unitop zur Verdeutlichung:

    [EventSubscriber(ObjectType::Table, Database::"Warehouse Activity Line", 'OnAfterValidateEvent', 'Lot No.', false, false)]
    local procedure OnAfterValidateWhseActLineLotNo(var Rec: Record "Warehouse Activity Line"; CurrFieldNo: Integer)
    var
        CertificateITMgt: Codeunit "GOB Certificate IT Mgt.";
    begin
        if Rec.IsTemporary() then
            exit;

        if Rec.FieldNo("Lot No.") <> CurrFieldNo then
            exit;

        CertificateITMgt.CheckLotOrSerialNoCertificateStatus(Rec."Item No.", Rec."Variant Code", "Item Tracking Type"::"Lot No.", Rec."Lot No.", Rec."Source Type", Rec."Source Subtype");
    end;

Keine Prüfung auf Lizenz oder Zugriffsrechte

Subscriber müssen mit Ausnahme von begründeten Einzelfällen immer zur Laufzeit mit Fehler abbrechen, wenn keine Objektlizenz und/oder keine Ausführungsberechtigung besteht. Anderenfalls werden Programmteile aufgrund von Lizenzfehlern bzw. Einrichtungsfehlern für den Anwender unerkannt nicht durchlaufen und Daten können inkonsistent werden.

SkipOnMissingLicense und SkipOnMissingPermissions müssen immer false sein.

Umgang mit mangelnder Erweiterbarkeit im Business Central Standard

Es kann vorkommen, dass im Standard von Business Central ein geeigneter Publisher fehlt und/oder eine Konstruktion anderweitig keine Erweiterbarkeit unterstützt. Dafür kann bei Microsoft ein Request erstellt werden. Microsoft entscheidet für zukünftige Versionen dies entsprechend einzufügen. Eine entsprechende Anforderung kann hier hinterlegt und verfolgt werden:

https://github.com/Microsoft/ALAppExtensions

In diesem Fall muss allerdings immer dann auch ein CU-Update auf den Stand erfolgen, der den Publisher dann enthält. Wenn zunächst ein Workaround implementiert werden muss, dann muss sofort auch eine Entwicklungsaufgabe für die ordentliche Implementierung ins Kunden-Backlog gelegt werden.

Umgang mit mangelnder Erweiterbarkeit im unitop Standard

Es kann vorkommen, dass im Standard von unitop ein geeigneter Publisher fehlt und/oder eine Konstruktion anderweitig keine Erweiterbarkeit unterstützt.

Für unitop Apps kann eine entsprechende Rückmeldung erfasst werden. GOB Mitarbeiter erfassen dazu intern entsprechende RFA. Mitarbeiter von Kunden oder Partnern wenden sich an den GOB Projektleiter.

In diesem Fall muss allerdings immer dann auch ein Update auf den Stand von unitop erfolgen, der den Publisher dann zukünftig enthält. Wenn zunächst ein Workaround implementiert werden muss, dann muss sofort auch eine Entwicklungsaufgabe für die ordentliche Implementierung ins Kunden-Backlog gelegt werden.

Code-Duplikation

Es kann vorkommen, dass der Code von Microsoft oder einem Partner nicht erweitert werden kann und stattdessen dupliziert werden muss. Das sind immer technische Schulden, daher ist dies auf das nötige Minimum zu begrenzen. Es gelten ferner folgende Regeln:

  • Die Duplikation ersetzt niemals eine anderweitig tragbare Lösung durch normale Extension-Mechanismen

  • Die Duplikation muss technisch eindeutig begründbar sein.

  • Die Duplikation wird je Funktion per Kommentar als solche kenntlich gemacht. Der Kommentar folgt diesem Schema:

    //This code has been copied from:
    //Module:
    //Object:
    //Version:
    //Reason:
    //Last re-merge with original source:
    

    Die Kommentare sind entsprechend zu aktualisieren, wenn der kopierte Code aktualisiert wird.

  • Der kopierte Code muss explizit nicht allen unseren Vorgaben folgen, die Struktur kann zum besseren späteren Vergleich mit dem Original beibehalten werden.

  • Der duplizierte Code wird immer mit Tests untersetzt, ggf. werden Tests der Quelle mit kopiert und/oder eigene Tests geschrieben. Ziel ist es, bei Versionswechsel Änderungen am Standard zu bemerken.

  • Der Code wird durch Pragmas von etwaigen Warnungen befreit

Performance

Folgende Technologie ist zwingend einzusetzen:

Folgende Technologien sind fallweise einzusetzen: