Restructure for Docsy theme
This commit is contained in:
parent
f4a1ebc077
commit
cceb0c945e
376 changed files with 963 additions and 186 deletions
26
content/docs/NAVPatterns/3-cal-coding-guidelines/_index.md
Normal file
26
content/docs/NAVPatterns/3-cal-coding-guidelines/_index.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
+++
|
||||
chapter = true
|
||||
title = "3. CAL Coding Guidelines"
|
||||
weight = 150
|
||||
+++
|
||||
We've decided to publish our current C/AL coding guidelines. They are actual, as per January 2015 when this is published (but might fall out of sync as time goes by).
|
||||
|
||||
You can also [download the C/AL coding guidelines as pdf,][anchor0] all in one document. In contrast, on this wiki, the coding guidelines are published individually. The reason is: give you the chance to comment and share your oppinion on each one. Thanks to [waldo][anchor1] for this idea and for helping out.
|
||||
|
||||
The guidelines are debatable - and it is good when they are generating debate. There is variation of opinion on the rules at Microsoft too. The plan is to simply expose what we use now. And more important, to say that guidelines could be used. Debate on individual guidelines can become heated for any programming language, but the benefit of using some guidelines stays.
|
||||
|
||||
For us, those guidelines are enforced at check-in time - we are using a tool which verifies and only allows compliant check-ins. While this tool is internal and not ready to publish, we had anyways decided to open up and present the rules we use to the community, as inspiration.
|
||||
|
||||
Question: Since we're having the guidelines, how come there is still C/AL code in NAV which doesn't respect them?
|
||||
|
||||
Answer: all new C/AL code is bound to follow the guidelines (else it cannot be checked-in). However, the code that existed before the rules - it does not. We had done cleanup in a certain degree. Now we're gradually improving the old code base as we visit various objects in order to add new functionality, however chances are that code we didn't touch in a long time had remained in its old form.
|
||||
|
||||
We're looking forward to your comments. Where you can, do provide concrete examples of the alternatives, Good and Bad.
|
||||
|
||||
{{< youtube z6skKy0pkmU >}}
|
||||
|
||||
|
||||
|
||||
[anchor0]: https://blogs.msdn.microsoft.com/nav/2015/01/09/cal-coding-guidelines-used-at-microsoft-development-center-copenhagen "download the C/AL coding guidelines as pdf"
|
||||
[anchor1]: /members/waldo/default.aspx "waldo"
|
||||
[anchor2]: https://www.youtube.com/watch?v=z6skKy0pkmU&list=PLhZ3P-LY7CqmVszuvtJLujFyHpsVN0U_w&index=26
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
+++
|
||||
title = "Design"
|
||||
weight = 490
|
||||
+++
|
||||
## C/AL Coding Guidelines
|
||||
|
||||
## **Design**
|
||||
|
||||
Find the C/AL guidelines by expanding the menu in the left.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
+++
|
||||
title = "By Reference Parameters"
|
||||
weight = 280
|
||||
+++
|
||||
Do not declare parameters by reference if their values are not intended to be changed.
|
||||
|
||||
Unintentional value changes might propagate. Also, it might lead people to believe that value changes are intended.
|
||||
|
||||
Bad code
|
||||
|
||||
LOCAL PROCEDURE ShowMessage@15(VAR Text@1000 : Text[250]);
|
||||
BEGIN
|
||||
Text := GetMessageText;
|
||||
IF (Text <> '') AND GenJnlLineInserted THEN
|
||||
MESSAGE(Text);
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
LOCAL PROCEDURE ShowMessage@15(Text@1000 : Text[250]);
|
||||
BEGIN
|
||||
Text := GetMessageText;
|
||||
IF (Text <> '') AND GenJnlLineInserted THEN
|
||||
MESSAGE(Text);
|
||||
END;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
+++
|
||||
title = "Class Coupling"
|
||||
weight = 320
|
||||
+++
|
||||
Do not write functions that have high class coupling. This makes the code hard to maintain.
|
||||
|
||||
Bad code
|
||||
|
||||
Any procedure / trigger that has class coupling of > 30
|
||||
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
Any procedure / trigger that has class coupling of <= 30\.
|
||||
Class coupling is computed by summing the unique instances of the following in a code block:
|
||||
- every unique usage of a complex C/AL data type (table, codeunit, etc) as 1\.
|
||||
- every unique usage of a DotNet type as 1\.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
+++
|
||||
title = "Cyclomatic Complexity"
|
||||
weight = 460
|
||||
+++
|
||||
Do not write functions that have high cyclomatic complexity. This makes the code hard to maintain.
|
||||
|
||||
Bad code
|
||||
|
||||
Any procedure / trigger that has a cyclomatic complexity > 25, using the CC3 version mentioned in [this article][anchor0].
|
||||
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
Any procedure / trigger that has a cyclomatic complexity <= 25, using the CC3 version.
|
||||
The CC3 version is computed by summing the following in a code block:
|
||||
- each IF statement as 1\.
|
||||
- each entire CASE as 1\.
|
||||
|
||||
|
||||
|
||||
[anchor0]: http://www.aivosto.com/project/help/pm-complexity.html
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
+++
|
||||
title = "Encapsulate Local Functionality"
|
||||
weight = 530
|
||||
+++
|
||||
Any function used local must be defined as local.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
+++
|
||||
title = "FINDSET FINDFIRST FINDLAST"
|
||||
weight = 600
|
||||
+++
|
||||
FINDSET, FIND('+') or FIND('-') should only be used when NEXT is used and vice versa.
|
||||
|
||||
Bad code
|
||||
|
||||
IF Cust.FIND('-') THEN
|
||||
ERROR(CustIsBlockErr)
|
||||
|
||||
Good code
|
||||
|
||||
IF Cust.FINDFIRST THEN
|
||||
ERROR(CustIsBlockErr)
|
||||
|
||||
Bad code
|
||||
|
||||
IF Cust.FINDFIRST THEN
|
||||
REPEAT
|
||||
...
|
||||
UNTIL Cust.NEXT = 0;
|
||||
|
||||
Good code
|
||||
|
||||
IF Cust.FINDSET THEN
|
||||
REPEAT
|
||||
...
|
||||
UNTIL Cust.NEXT = 0;
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
+++
|
||||
title = "Initialized Variables"
|
||||
weight = 660
|
||||
+++
|
||||
Variables should always be set to a specific value, before they are used.
|
||||
|
||||
Bad code
|
||||
|
||||
PROCEDURE SetPurchLine@22(VAR CurrentPurchLine@1000 : Record 39);
|
||||
VAR
|
||||
Pegging@1001 : Boolean;
|
||||
BEGIN
|
||||
IF Pegging THEN
|
||||
CurrQuantity := CurrentPurchLine."Quantity (Base)"
|
||||
ELSE
|
||||
CurrQuantity := CurrentPurchLine."Outstanding Qty. (Base)";
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
PROCEDURE SetPurchLine@22(VAR CurrentPurchLine@1000 : Record 39);
|
||||
VAR
|
||||
Pegging@1001 : Boolean;
|
||||
BEGIN
|
||||
Pegging := IsPegging(CurrentPurchLine);
|
||||
IF Pegging THEN
|
||||
CurrQuantity := CurrentPurchLine."Quantity (Base)"
|
||||
ELSE
|
||||
CurrQuantity := CurrentPurchLine."Outstanding Qty. (Base)";
|
||||
END;
|
||||
|
||||
Bad code
|
||||
|
||||
// In the example below, the function will always return FALSE.
|
||||
PROCEDURE GetItemsToPlan@22() : Boolean;
|
||||
BEGIN
|
||||
SETRANGE("Document Type","Document Type"::Order);
|
||||
...
|
||||
FINDSET
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
PROCEDURE GetItemsToPlan@22() : Boolean;
|
||||
BEGIN
|
||||
SETRANGE("Document Type","Document Type"::Order);
|
||||
...
|
||||
EXIT(FINDSET)
|
||||
END;
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
+++
|
||||
title = "Maintainability Index"
|
||||
weight = 770
|
||||
+++
|
||||
[Maintainability Index][anchor0]: Do not write functions that have a very low maintainability index. This makes the code hard to maintain.
|
||||
|
||||
Bad code
|
||||
|
||||
Any procedure / trigger that has a maintainability index < 20
|
||||
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
Any procedure / trigger that has a maintainability index >= 20\.
|
||||
The Maintainability Index is computed as a function:
|
||||
- Lines Of Code (inverse proportional)
|
||||
- the Halstead Volume
|
||||
- Cyclomatic Complexity (inverse proportional).
|
||||
|
||||
More info
|
||||
|
||||
* [Halstead Volume][anchor1]
|
||||
* [Cyclomatic Complexity][anchor2]
|
||||
|
||||
Bad code
|
||||
|
||||
Any procedure / trigger that is > 100 lines of code
|
||||
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
Any procedure / trigger that is <= 100 lines of code.
|
||||
A full C/AL Statement counts as 1 line of code
|
||||
|
||||
|
||||
|
||||
[anchor0]: http://blogs.msdn.com/b/codeanalysis/archive/2007/11/20/maintainability-index-range-and-meaning.aspx
|
||||
[anchor1]: http://en.wikipedia.org/wiki/Halstead_complexity_measures
|
||||
[anchor2]: http://www.aivosto.com/project/help/pm-complexity.html
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
+++
|
||||
title = "Parameter Placeholders"
|
||||
weight = 920
|
||||
+++
|
||||
The number of parameters passed to a string must match the placeholders.
|
||||
|
||||
Bad code
|
||||
|
||||
CannotDeleteLineErr@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.';
|
||||
...
|
||||
ERROR(CannotDeleteLineErr,TABLECAPTION);
|
||||
|
||||
Good code
|
||||
|
||||
CannotDeleteLineErr@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.';
|
||||
...
|
||||
ERROR(CannotDeleteLineErr);
|
||||
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
CannotUseThisFieldErr@1020 : TextConst 'ENU=You cannot use this field for %2 fields.';
|
||||
...
|
||||
ERROR(CannotUseThisFieldErr,0,Field.Class);
|
||||
|
||||
Good code
|
||||
|
||||
CannotUseThisFieldErr@1020 : TextConst 'ENU=You cannot use this field for %1 fields.';
|
||||
...
|
||||
ERROR(CannotUseThisFieldErr,Field.Class);
|
||||
|
||||
###
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
+++
|
||||
title = "Static Object Invocation"
|
||||
weight = 1160
|
||||
+++
|
||||
Call objects statically whenever possible. It reduces extra noise and removes extra variables. Downside: changing the name of the object which is called statically will need a code update.
|
||||
|
||||
Bad code
|
||||
|
||||
LOCAL PROCEDURE Code@1();
|
||||
VAR
|
||||
CAJnlPostBatch@1001 : Codeunit 1103;
|
||||
BEGIN
|
||||
CAJnlPostBatch.Run(CostJnlLine);
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
LOCAL PROCEDURE Code@1();
|
||||
BEGIN
|
||||
CODEUNIT.RUN(CODEUNIT::"CA Jnl.-Post Batch",CostJnlLine);
|
||||
END;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
+++
|
||||
title = "Unreachable Code"
|
||||
weight = 1310
|
||||
+++
|
||||
Do not write code that will never be hit.
|
||||
|
||||
It affects code readability and can lead to wrong assumptions.
|
||||
|
||||
Bad code
|
||||
|
||||
IF Type <> Type::FIELD THEN BEGIN
|
||||
...
|
||||
ERROR(...);
|
||||
RecRef.CLOSE;
|
||||
END;
|
||||
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
IF Type <> Type::FIELD THEN BEGIN
|
||||
...
|
||||
RecRef.CLOSE;
|
||||
ERROR(...);
|
||||
END;
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
+++
|
||||
title = "Unused Initialized Variables"
|
||||
weight = 1320
|
||||
+++
|
||||
The value assigned to a variable must be used. Else the variable is not necessary.
|
||||
|
||||
Bad code
|
||||
|
||||
PROCEDURE AddEntities@1(FilterStr@1000 : Text[250]);
|
||||
VAR
|
||||
Vendor@1001 : Record 23;
|
||||
Count@1002 : Integer;
|
||||
BEGIN
|
||||
Count := 0;
|
||||
Vendor.SETFILTER("No.",FilterStr);
|
||||
IF Vendor.FINDSET THEN
|
||||
REPEAT
|
||||
"User ID" := USERID;
|
||||
"Vendor No." := Vendor."No.";
|
||||
IF INSERT THEN
|
||||
Count += 1;
|
||||
UNTIL Vendor.NEXT = 0;
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
PROCEDURE AddEntities@1(FilterStr@1000 : Text[250]);
|
||||
VAR
|
||||
Vendor@1001 : Record 23;
|
||||
BEGIN
|
||||
Vendor.SETFILTER("No.",FilterStr);
|
||||
IF Vendor.FINDSET THEN
|
||||
REPEAT
|
||||
"User ID" := USERID;
|
||||
"Vendor No." := Vendor."No.";
|
||||
IF INSERT THEN;
|
||||
UNTIL Vendor.NEXT = 0;
|
||||
END;
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
+++
|
||||
title = "Unused Variables"
|
||||
weight = 1330
|
||||
+++
|
||||
Do not declare variables that are unused.
|
||||
|
||||
Unused variables affect readability.
|
||||
|
||||
Bad code
|
||||
|
||||
PROCEDURE CheckPostingDate@23(CaptionEntryNo@1005 : Text[50]);
|
||||
BEGIN
|
||||
IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN
|
||||
ERROR(DateNotAllowedErr,Caption,EntryNo)
|
||||
IF PostingDate > MaxPostingDate THEN
|
||||
MaxPostingDate := PostingDate;
|
||||
END
|
||||
|
||||
Good code
|
||||
|
||||
PROCEDURE CheckPostingDate@23();
|
||||
BEGIN
|
||||
IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN
|
||||
ERROR(DateNotAllowedErr,Caption,EntryNo);
|
||||
IF PostingDate > MaxPostingDate THEN
|
||||
MaxPostingDate := PostingDate;
|
||||
END;
|
||||
|
||||
Bad code
|
||||
|
||||
PROCEDURE IsReturned@14(EntryNo@1002 : Integer) : Decimal;
|
||||
VAR
|
||||
ItemEntry@1000 : Record 32;
|
||||
Quantity@1003 : Integer;
|
||||
BEGIN
|
||||
EXIT(-OutboundApplied(EntryNo,TRUE) - InboundApplied(EntryNo,TRUE));
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
PROCEDURE IsReturned@14(EntryNo@1002 : Integer) : Decimal;
|
||||
BEGIN
|
||||
EXIT(-OutboundApplied(EntryNo,TRUE) - InboundApplied(EntryNo,TRUE));
|
||||
END;
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
+++
|
||||
title = "Variable Capacity Mismatch"
|
||||
weight = 1410
|
||||
+++
|
||||
Do not assign a value to a variable whose capacity is smaller.
|
||||
|
||||
It will throw an error at runtime.
|
||||
|
||||
Bad code
|
||||
|
||||
FileName@1010 : Text[250];
|
||||
...
|
||||
UploadedFileName@1016 : Text[1024];
|
||||
...
|
||||
FileName := UploadedFileName;
|
||||
|
||||
Good code
|
||||
|
||||
FileName@1010 : Text[1024];
|
||||
...
|
||||
UploadedFileName@1016 : Text[1024];
|
||||
...
|
||||
FileName := UploadedFileName;
|
||||
|
||||
Bad code
|
||||
|
||||
FileName@1010 : Text[250];
|
||||
...
|
||||
UploadedFileName@1016 : Text[1024];
|
||||
...
|
||||
FileName := UploadedFileName;
|
||||
|
||||
Good code
|
||||
|
||||
FileName@1010 : Text[250];
|
||||
...
|
||||
UploadedFileName@1016 : Text[1024];
|
||||
...
|
||||
FileName := COPYSTR(UploadedFileName,1,250); // In case only the first 250 chars are needed. Similar for fields
|
||||
|
||||
Bad code
|
||||
|
||||
VAR
|
||||
ExceededNumberTxt@001 : 'ENU=Warning: Exceeded number of unsent documents/requests'
|
||||
Subject@1002 : Text[50];
|
||||
...
|
||||
BEGIN
|
||||
...
|
||||
Subject := ExceededNumberTxt;
|
||||
|
||||
Good code
|
||||
|
||||
VAR
|
||||
ExceededNumberTxt@001 : 'ENU=Warning: Exceeded number of unsent documents/requests'
|
||||
Subject@1002 : Text[100];
|
||||
...
|
||||
BEGIN
|
||||
...
|
||||
Subject := ExceededNumberTxt';
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
+++
|
||||
title = "WITH Scope Name Collision"
|
||||
weight = 1450
|
||||
+++
|
||||
Do not use the WITH scope when it has a variable whose name is the same as a local variable. This can lead to wrong code assumptions.
|
||||
|
||||
**Given that**
|
||||
"Contract Type" is a field on table ServiceContractHeader, then in the following example there is a parameter name clash with the field name. Which one will be used?
|
||||
|
||||
Bad code
|
||||
|
||||
PROCEDURE InsertData@1("Contract Type"@1000 : Option...);
|
||||
...
|
||||
BEGIN
|
||||
...
|
||||
WITH ServiceContractHeader DO BEGIN
|
||||
...
|
||||
DimMgt.InsertServContractDim(...,"Contract Type","Contract No.",0,...);
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
PROCEDURE InsertData@1(ContractType@1000 : Option...);
|
||||
...
|
||||
BEGIN
|
||||
...
|
||||
WITH ServiceContractHeader DO BEGIN
|
||||
...
|
||||
DimMgt.InsertServContractDim(...,ContractType,"Contract No.",0,...);
|
||||
END;
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
+++
|
||||
title = "Internally used DotNet Types"
|
||||
weight = 690
|
||||
+++
|
||||
_(Dynamics NAV 2015)_
|
||||
|
||||
|
||||
**Dot Net Types**
|
||||
|
||||
'mscorlib'.System.Convert
|
||||
|
||||
'mscorlib'.System.Globalization.CultureInfo
|
||||
|
||||
'mscorlib'.System.Globalization.DateTimeStyles
|
||||
|
||||
'mscorlib'.System.Globalization.NumberStyles
|
||||
|
||||
'mscorlib'.System.Type
|
||||
|
||||
'mscorlib'.System.Array
|
||||
|
||||
'mscorlib'.System.EventArgs
|
||||
|
||||
'mscorlib'.System.Security.Cryptography.SHA512Managed
|
||||
|
||||
'mscorlib'.System.Security.Cryptography.HashAlgorithm
|
||||
|
||||
'mscorlib'.System.Text.Encoding
|
||||
|
||||
'mscorlib'.System.Text.UTF8Encoding
|
||||
|
||||
'mscorlib'.System.Environment
|
||||
|
||||
'mscorlib'.System.IO.Directory
|
||||
|
||||
'mscorlib'.System.IO.Path
|
||||
|
||||
'mscorlib'.System.IO.File
|
||||
|
||||
'mscorlib'.System.IO.FileAttributes
|
||||
|
||||
'mscorlib'.System.Collections.ArrayList
|
||||
|
||||
'mscorlib'.System.Collections.IEnumerator
|
||||
|
||||
'mscorlib'.System.Collections.Generic.IEnumerator\`1
|
||||
|
||||
'mscorlib'.System.TimeSpan
|
||||
|
||||
'mscorlib'.System.DateTime
|
||||
|
||||
'mscorlib'.System.DateTimeKind
|
||||
|
||||
'mscorlib'.System.DateTimeOffset
|
||||
|
||||
'mscorlib'.System.Decimal
|
||||
|
||||
'mscorlib'.System.String
|
||||
|
||||
'System'.System.Diagnostics.Process
|
||||
|
||||
'System'.System.Diagnostics.ProcessStartInfo
|
||||
|
||||
'System'.System.IO.Compression.CompressionMode
|
||||
|
||||
'System'.System.IO.Compression.GZipStream
|
||||
|
||||
'System'.System.Uri
|
||||
|
||||
'System'.System.UriPartial
|
||||
|
||||
'System.Data'.System.Data.DataColumn
|
||||
|
||||
'System.Data'.System.Data.DataTable
|
||||
|
||||
'System.Data'.System.Data.DataRow
|
||||
|
||||
'System.Web'.System.Web.HttpUtility
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.DialogResult
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.FileDialog
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.OpenFileDialog
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.SaveFileDialog
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.FolderBrowserDialog
|
||||
|
||||
'System.Xml'.\*
|
||||
|
||||
'DocumentFormat.OpenXml'.\*
|
||||
|
||||
'mscorlib'.System.IO.DirectoryInfo
|
||||
|
||||
'mscorlib'.System.IO.FileInfo
|
||||
|
||||
'Microsoft.Dynamics.Nav.Client.CodeViewerTypes'.Microsoft.Dynamics.Nav.Client.CodeViewerTypes.BreakpointCollection
|
||||
|
||||
'Microsoft.Dynamics.Nav.Client.CodeViewerTypes'.Microsoft.Dynamics.Nav.Client.CodeViewerTypes.VariableCollection
|
||||
|
||||
'Microsoft.Dynamics.Nav.SMTP'.Microsoft.Dynamics.Nav.SMTP.SmtpMessage
|
||||
|
||||
'Microsoft.Dynamics.Nav.Management.DSObjectPickerWrapper'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.Timer'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.DO.ClientProxyWrapper'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.Client.BusinessChart'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.Client.BusinessChart.Model'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.Integration.Office'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.Integration.Office.Mock'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.EwsWrapper'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.EwsWrapper.ALTestHelper'.\*
|
||||
|
||||
'Microsoft.Dynamics.NAV.OLSync.OLSyncSupplier'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.OLSync.Common'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.NavUserAccount'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.OpenXml'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.RapidStart'.\*
|
||||
|
||||
'Microsoft.Dynamics.Framework.RapidStart.Common'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.Client.TimelineVisualization'.Microsoft.Dynamics.Nav.Client.TimelineVisualization.
|
||||
|
||||
VisualizationScenarios
|
||||
|
||||
'Microsoft.Dynamics.Framework.UI.WinForms.DataVisualization.Timeline'.Microsoft.Dynamics.Framework.UI.
|
||||
|
||||
WinForms.DataVisualization.TimelineVisualization.DataModel+TransactionChangesRow
|
||||
|
||||
'Microsoft.Dynamics.Framework.UI.WinForms.DataVisualization.Timeline'.Microsoft.Dynamics.Framework.UI.
|
||||
|
||||
WinForms.DataVisualization.TimelineVisualization.DataModel+TransactionChangesDataTable
|
||||
|
||||
'Microsoft.Dynamics.Framework.UI.WinForms.DataVisualization.Timeline'.Microsoft.Dynamics.Framework.UI.
|
||||
|
||||
WinForms.DataVisualization.TimelineVisualization.DataModel+TransactionRow
|
||||
|
||||
'Microsoft.Dynamics.Framework.UI.WinForms.DataVisualization.Timeline'.Microsoft.Dynamics.Framework.UI.
|
||||
|
||||
WinForms.DataVisualization.TimelineVisualization.DataModel+TransactionDataTable
|
||||
|
||||
'Microsoft.Office.Interop.Word'.\*
|
||||
|
||||
'Microsoft.Office.Interop.Excel'.\*
|
||||
|
||||
'Microsoft.Dynamics.BAPIWrapper'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.Types'.Microsoft.Dynamics.Nav.Types.ConfigSettings
|
||||
|
||||
'Microsoft.Dynamics.Nav.DocumentService'.\*
|
||||
|
||||
'Microsoft.Dynamics.Nav.DocumentService.Types'.\*
|
||||
|
||||
'mscorlib'.System.IO.StreamWriter
|
||||
|
||||
'Microsoft.Dynamics.Nav.Client.TimelineVisualization'.Microsoft.Dynamics.Nav.Client.TimelineVisualization.
|
||||
|
||||
InteractiveTimelineVisualizationAddIn
|
||||
|
||||
'System'.System.ComponentModel.CancelEventArgs
|
||||
|
||||
'System'.System.Text.RegularExpressions.Regex
|
||||
|
||||
'System'.System.Text.RegularExpressions.RegexOptions
|
||||
|
||||
'mscorlib'.System.IO.StreamReader
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.Control
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.ControlEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.DragEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.GiveFeedbackEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.HelpEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.InvalidateEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.KeyEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.KeyPressEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.LayoutEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.MouseEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.PaintEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.PreviewKeyDownEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.QueryAccessibilityHelpEventArgs
|
||||
|
||||
'System.Windows.Forms'.System.Windows.Forms.UICuesEventArgs
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
+++
|
||||
title = "Internationalization"
|
||||
weight = 700
|
||||
+++
|
||||
## C/AL Coding Guidelines
|
||||
|
||||
## **Internationalization**
|
||||
|
||||
|
||||
Find the C/AL guidelines by expanding the menu in the left.
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
+++
|
||||
title = "Using Calcdate"
|
||||
weight = 1370
|
||||
+++
|
||||
CALCDATE should only be used with DateFormula variables. Alternatively the string should be enclosed using the <> symbols.
|
||||
|
||||
Bad code
|
||||
|
||||
IF ReservEntry."Expected Receipt Date" >
|
||||
CALCDATE('-' + FORMAT("Dampener (Time)") + FirstDate)
|
||||
THEN
|
||||
|
||||
Good code
|
||||
|
||||
IF ReservEntry."Expected Receipt Date" >
|
||||
CALCDATE('<-' + FORMAT("Dampener (Time)") + FirstDate + '>')
|
||||
THEN
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
+++
|
||||
title = "Localizability"
|
||||
weight = 750
|
||||
+++
|
||||
## C/AL Coding Guidelines
|
||||
|
||||
## **Localizability**
|
||||
|
||||
Find the C/AL guidelines by expanding the menu in the left.
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
+++
|
||||
title = "CaptionML on System Pages"
|
||||
weight = 300
|
||||
+++
|
||||
CaptionML should always be specified on a page field for a system table. By default, system tables do not have captions, so if you need to use them in the UI then captions need to be added.
|
||||
|
||||
Bad code
|
||||
|
||||
...
|
||||
{ 2 ;2 ;Field ;
|
||||
SourceExpr=Name }
|
||||
...
|
||||
OBJECT Table 2000000000 User
|
||||
...
|
||||
{ 2 ; ;Name ;Text50 }
|
||||
|
||||
Good code
|
||||
|
||||
...
|
||||
{ 2 ;2 ;Field ;
|
||||
CaptionML=ENU=Name;
|
||||
SourceExpr=Name }
|
||||
...
|
||||
OBJECT Table 2000000000 User
|
||||
...
|
||||
{ 2 ; ;Name ;Text50 }
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
+++
|
||||
title = "FIELDCAPTION and TABLECAPTION"
|
||||
weight = 580
|
||||
+++
|
||||
For user messages, errors etc., use FIELDCAPTION not FIELDNAME and TABLECAPTION not TABLENAME.
|
||||
|
||||
Reason:
|
||||
|
||||
1. The correct translation will be automatically used.
|
||||
2. If the caption/name changes, then there will be a single point of change needed.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF NOT CONFIRM(UpdateLocationQst,TRUE,FIELDNAME("Location Code"),...)
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF NOT CONFIRM(UpdateLocationQst,TRUE,FIELDCAPTION("Location Code"),...)
|
||||
```
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
+++
|
||||
title = "Global Text Constants"
|
||||
weight = 610
|
||||
+++
|
||||
Declare Text Constant as global variables.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
PROCEDURE GetRequirementText@6(...) : Text[50];
|
||||
VAR
|
||||
RequirementOptionsTxt@1002 : TextConst 'ENU=Shipment,Receive,Pick,Put-Away';
|
||||
BEGIN
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
VAR
|
||||
RequirementOptionsTxt@1002 : TextConst 'ENU=Shipment,Receive,Pick,Put-Away';
|
||||
...
|
||||
PROCEDURE GetRequirementText@6(...) : Text[50];
|
||||
BEGIN
|
||||
```
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
+++
|
||||
title = "Use Text Constants"
|
||||
weight = 1360
|
||||
+++
|
||||
Pass user messages using Text Constants. It makes translation easy.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
ImportAttachmentQst@1021 : TextConst 'ENU="Import attachment "';
|
||||
...
|
||||
IF CONFIRM(ImportAttachmentQst + Caption +'?',TRUE) THEN BEGIN
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
ImportAttachmentQst@1021 : TextConst 'ENU="Import attachment %1?"';
|
||||
...
|
||||
IF CONFIRM(STRSUBSTNO(ImportAttachmentQst, Caption),TRUE) THEN BEGIN
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
...
|
||||
IF NOT
|
||||
CONFIRM(
|
||||
STRSUBSTNO(
|
||||
'Difference on Periodic entries: %1 on %2' +
|
||||
'Do you want to continue?',Balance,Date),
|
||||
TRUE)
|
||||
THEN
|
||||
ERROR('Program terminated by the user');
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
DiffOnPeriodEntiesQst@100 : TextConst 'ENU="Difference on Periodic entries: %1 on %2\\ Do you want to continue?"';
|
||||
ProgramTerminatedErr@200 : TextConst 'ENU="Program terminated by the user"';
|
||||
...
|
||||
IF NOT CONFIRM(STRSUBSTNO(DiffOnPeriodEntiesQst,Balance,Date),TRUE) THEN
|
||||
ERROR(ProgramTerminatedErr);
|
||||
```
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
+++
|
||||
title = "Using OptionCaptionML"
|
||||
weight = 1380
|
||||
+++
|
||||
The OptionCaptionML should be filled in for sourceexpression using option data types.
|
||||
|
||||
Bad code
|
||||
|
||||
{ 30 ;TextBox ;17850;0 ;150 ;423 ;Name=Selection;
|
||||
SourceExpr=Selection;
|
||||
DataSetFieldName=Selection }
|
||||
...
|
||||
Selection@1008 : 'Open,Closed,Open and Closed';
|
||||
...
|
||||
|
||||
Good code
|
||||
|
||||
{ 30 ;TextBox ;17850;0 ;150 ;423 ;Name=Selection;
|
||||
OptionCaptionML=ENU=Open,Closed,Open and Closed;
|
||||
SourceExpr=Selection;
|
||||
DataSetFieldName=Selection }
|
||||
...
|
||||
Selection@1008 : 'Open,Closed,Open and Closed';
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
+++
|
||||
title = "Readability"
|
||||
weight = 980
|
||||
+++
|
||||
## C/AL Coding Guidelines
|
||||
|
||||
## **Readability**
|
||||
|
||||
Generally, all readability rules are Microsoft style choices only. You can use them to keep consistency with the existing code.
|
||||
|
||||
Find the C/AL guidelines by expanding the menu in the left.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
+++
|
||||
title = "Begin as an 'After Word'"
|
||||
weight = 230
|
||||
+++
|
||||
When BEGIN follows THEN, ELSE, DO, it should be on the same line, preceded by one space character.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF ICPartnerRefType = ICPartnerRefType::"Common Item No." THEN
|
||||
BEGIN
|
||||
...
|
||||
END;
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```
|
||||
IF ICPartnerRefType = ICPartnerRefType::"Common Item No." THEN BEGIN
|
||||
...
|
||||
END;
|
||||
```
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
+++
|
||||
title = "Begin-End - Compound Only"
|
||||
weight = 240
|
||||
+++
|
||||
Only use BEGIN..END to enclose compound statements.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF FINDSET THEN BEGIN
|
||||
REPEAT
|
||||
...
|
||||
UNTIL NEXT = 0;
|
||||
END;
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF FINDSET THEN
|
||||
REPEAT
|
||||
...
|
||||
UNTIL NEXT = 0;
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF IsAssemblyOutputLine THEN BEGIN
|
||||
TESTFIELD("Order Line No.",0);
|
||||
END;
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF IsAssemblyOutputLine THEN
|
||||
TESTFIELD("Order Line No.",0);
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF FINDSET THEN
|
||||
REPEAT
|
||||
BEGIN
|
||||
...
|
||||
END;
|
||||
UNTIL NEXT = 0;
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF FINDSET THEN
|
||||
REPEAT
|
||||
...
|
||||
UNTIL NEXT = 0;
|
||||
```
|
||||
|
||||
Exception
|
||||
|
||||
```al
|
||||
// Except for this case
|
||||
IF X THEN BEGIN
|
||||
IF Y THEN
|
||||
DO SOMETHING;
|
||||
END ELSE (not X)
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
+++
|
||||
title = "Binary Operator to Start Line"
|
||||
weight = 250
|
||||
+++
|
||||
Do not start a line with a binary operator.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
"Quantity to Ship" :=
|
||||
Quantity
|
||||
- "Quantity Shipped"
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
"Quantity to Ship" :=
|
||||
Quantity -
|
||||
"Quantity Shipped"
|
||||
```
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
+++
|
||||
title = "Blank Lines"
|
||||
weight = 260
|
||||
+++
|
||||
Do not use blank lines at the beginning or end of any functions, after BEGIN, before END, or inside multiline expressions.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
PROCEDURE MATRIX_OnDrillDown@1133(MATRIX_ColumnOrdinal : Integer);
|
||||
BEGIN
|
||||
|
||||
SetupDrillDownCol(MATRIX_ColumnOrdinal);
|
||||
DrillDown(FALSE,ValueType);
|
||||
|
||||
END;
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
PROCEDURE MATRIX_OnDrillDown@1133(MATRIX_ColumnOrdinal : Integer);
|
||||
BEGIN
|
||||
SetupDrillDownCol(MATRIX_ColumnOrdinal);
|
||||
DrillDown(FALSE,ValueType);
|
||||
END;
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF NameIsValid AND
|
||||
|
||||
Name2IsValid
|
||||
THEN
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF NameIsValid AND
|
||||
Name2IsValid
|
||||
THEN
|
||||
```
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
+++
|
||||
title = "CASE Action"
|
||||
weight = 310
|
||||
+++
|
||||
A CASE action should start on a line after the possibility.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
CASE Letter OF
|
||||
'A': Letter2 := '10';
|
||||
'B': Letter2 := '11';
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
CASE Letter OF
|
||||
'A':
|
||||
Letter2 := '10';
|
||||
'B':
|
||||
Letter2 := '11';
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
+++
|
||||
title = "Colon usage in CASE"
|
||||
weight = 340
|
||||
+++
|
||||
The last possibility on a CASE statement must be immediately followed by a colon.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
CASE DimOption OF
|
||||
DimOption::"Global Dimension 1" :
|
||||
DimValue."Dimension Code" := GLSetup."Global Dimension 1 Code";
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
CASE DimOption OF
|
||||
DimOption::"Global Dimension 1":
|
||||
DimValue."Dimension Code" := GLSetup."Global Dimension 1 Code";
|
||||
```
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
+++
|
||||
title = "Comments inside Curly Brackets"
|
||||
weight = 350
|
||||
+++
|
||||
Never use curly bracket comments. During development, the "Block comment" functionality can be used instead. However, in production code, block comments are not recommended.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
PeriodTxt: {Period}
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
PeriodTxt: // Period
|
||||
```
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
PROCEDURE MATRIX_OnAfterGetRecord@10(MATRIX_ColumnOrdinal : Integer);
|
||||
BEGIN
|
||||
{
|
||||
IF ShowColumnName THEN
|
||||
MatrixHeader := MatrixRecords[MATRIX_ColumnOrdinal].Name
|
||||
ELSE
|
||||
MatrixHeader := MatrixRecords[MATRIX_ColumnOrdinal].Code;
|
||||
}
|
||||
MatrixRecord := MatrixRecords[MATRIX_ColumnOrdinal];
|
||||
AnalysisValue := CalcAmt(ValueType,TRUE);
|
||||
MATRIX_CellData[MATRIX_ColumnOrdinal] := AnalysisValue;
|
||||
END;
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
PROCEDURE MATRIX_OnAfterGetRecord@10(MATRIX_ColumnOrdinal : Integer);
|
||||
BEGIN
|
||||
MatrixRecord := MatrixRecords[MATRIX_ColumnOrdinal];
|
||||
AnalysisValue := CalcAmt(ValueType,TRUE);
|
||||
MATRIX_CellData[MATRIX_ColumnOrdinal] := AnalysisValue;
|
||||
END;
|
||||
```
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
+++
|
||||
title = "Comment Spacing"
|
||||
weight = 360
|
||||
+++
|
||||
Always start comments with // followed by one space character.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
RowNo += 1000; //Move way below the budget
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
RowNo += 1000; // Move way below the budget
|
||||
```
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
+++
|
||||
title = "END ELSE Pair"
|
||||
weight = 540
|
||||
+++
|
||||
The END ELSE pair should always appear on the same line.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF OppEntry.FIND('-') THEN
|
||||
IF SalesCycleStage.FIND('-') THEN BEGIN
|
||||
...
|
||||
END
|
||||
ELSE
|
||||
...
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF OppEntry.FIND('-') THEN
|
||||
IF SalesCycleStage.FIND('-') THEN BEGIN
|
||||
...
|
||||
END ELSE
|
||||
...
|
||||
```
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
+++
|
||||
title = "Indentation"
|
||||
weight = 650
|
||||
+++
|
||||
In general, use an indentation of two space characters. Logical expressions in the IF, WHILE, and UNTIL parts are indented at least 3, 6, and 6 spaces respectively.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF GLSetup."Unrealized VAT" OR
|
||||
(GLSetup."Prepayment Unrealized VAT" AND NewCVLedgEntryBuf.Prepayment)
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF GLSetup."Unrealized VAT" OR
|
||||
(GLSetup."Prepayment Unrealized VAT" AND NewCVLedgEntryBuf.Prepayment)
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF GenJnlLine."Account No." <> ICPartner.Code THEN
|
||||
ICPartner.GET("Account No.");
|
||||
IF GenJnlLine.Amount \> 0 THEN BEGIN
|
||||
...
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF GenJnlLine."Account No." <> ICPartner.Code THEN
|
||||
ICPartner.GET("Account No.");
|
||||
IF GenJnlLine.Amount > 0 THEN BEGIN
|
||||
...
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
Dialog.OPEN(WindowTxt +
|
||||
'@1@@@@@@@@@@@@@@@@@@@@@@@');
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
Dialog.OPEN(
|
||||
WindowTxt +
|
||||
'@1@@@@@@@@@@@@@@@@@@@@@@@');
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
TempOldCustLedgEntry.DELETE;
|
||||
// Find the next old entry for application of the new entry
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
TempOldCustLedgEntry.DELETE;
|
||||
// Find the next old entry for application of the new entry
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF NOT ("Applies-to Doc. Type" IN
|
||||
["Applies-to Doc. Type"::Receipt,
|
||||
"Applies-to Doc. Type"::"Return Shipment"])
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF NOT ("Applies-to Doc. Type" IN
|
||||
["Applies-to Doc. Type"::Receipt,
|
||||
"Applies-to Doc. Type"::"Return Shipment"])
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
WHILE (RemAmt > 0) OR
|
||||
(RemAmtLCY > 0)
|
||||
DO
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
WHILE (RemAmt > 0) OR
|
||||
(RemAmtLCY > 0)
|
||||
DO
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
UNTIL (RemAmt > 0) AND
|
||||
(RemAmtLCY > 0);
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
UNTIL (RemAmt > 0) AND
|
||||
(RemAmtLCY > 0)
|
||||
```
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
+++
|
||||
title = "Keyword Pairs - Indentation"
|
||||
weight = 730
|
||||
+++
|
||||
The IF..THEN pair, WHILE..DO pair, and FOR..DO pair must appear on the same line or the same level of indentation.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF (x = y) AND
|
||||
(a = b) THEN
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF (x = y) AND
|
||||
(a = b)
|
||||
THEN
|
||||
```
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
+++
|
||||
title = "Line Start Keywords"
|
||||
weight = 740
|
||||
+++
|
||||
The END, IF, REPEAT, FOR, WHILE, ELSE and CASE statement should always start a line.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF IsContactName THEN ValidateContactName
|
||||
ELSE IF IsSalespersonCode THEN ValidateSalespersonCode
|
||||
ELSE IF IsSalesCycleCode THEN ValidatSalesCycleCode;
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF IsContactName THEN
|
||||
ValidateContactName
|
||||
ELSE
|
||||
IF IsSalespersonCode THEN
|
||||
ValidateSalespersonCode
|
||||
ELSE
|
||||
IF IsSalesCycleCode THEN
|
||||
ValidatSalesCycleCode;
|
||||
```
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
+++
|
||||
title = "Lonely Repeat"
|
||||
weight = 760
|
||||
+++
|
||||
The REPEAT statement should always be alone on a line.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF ReservEntry.FINDSET THEN REPEAT
|
||||
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF ReservEntry.FINDSET THEN
|
||||
REPEAT
|
||||
```
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
+++
|
||||
title = "Named Invocations"
|
||||
weight = 830
|
||||
+++
|
||||
When calling an object statically use the name, not the number
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
PAGE.RUNMODAL(525,SalesShptLine)
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
PAGE.RUNMODAL(PAGE::"Posted Sales Shipment Lines",SalesShptLine)
|
||||
```
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
+++
|
||||
title = "Nested WITHs"
|
||||
weight = 850
|
||||
+++
|
||||
Do not nest WITHs that reference different types of objects.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
WITH PostedWhseShptLine DO BEGIN
|
||||
...
|
||||
WITH ItemLedgEntry DO
|
||||
InsertBufferRec(...,"Serial No.","Lot No.",...);
|
||||
...
|
||||
END;
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
WITH PostedWhseShptLine DO BEGIN
|
||||
...
|
||||
InsertBufferRec(...,ItemLedgEntry."Serial No.",ItemLedgEntry."Lot No.",...);
|
||||
...
|
||||
END;
|
||||
```
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
+++
|
||||
title = "One Statement Per Line"
|
||||
weight = 910
|
||||
+++
|
||||
A line of code should not have more than one statement.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF OppEntry.FIND('-') THEN EXIT
|
||||
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF OppEntry.FIND('-') THEN
|
||||
EXIT
|
||||
|
||||
```
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
TotalCost += Cost; TotalAmt += Amt;
|
||||
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
TotalCost += Cost;
|
||||
TotalAmt += Amt;
|
||||
```
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
+++
|
||||
title = "Separate IF and ELSE"
|
||||
weight = 1050
|
||||
+++
|
||||
IF and ELSE statements should be on separate lines.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF Atom[i+1] = '>' THEN HasLogicalOperator := TRUE ELSE BEGIN
|
||||
...
|
||||
END;
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF Atom[i+1] = '>' THEN
|
||||
HasLogicalOperator := TRUE
|
||||
ELSE BEGIN
|
||||
...
|
||||
END;
|
||||
```
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
+++
|
||||
title = "Spacing Binary Operators"
|
||||
weight = 1120
|
||||
+++
|
||||
There must be exactly one space character on each side of a binary operator such as = + - AND OR =. The parameter comma operator however, should have no spaces.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
"Line Discount %" := "Line Discount Amount"/"Line Value"*100
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
"Line Discount %" := "Line Discount Amount" / "Line Value" * 100;
|
||||
```
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
StartDate := CALCDATE('<+'+FORMAT(Days + i)+'D>', StartDate);
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
StartDate := CALCDATE('<+' + FORMAT(Days + i) + 'D>',StartDate);
|
||||
```
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
StartDate := 0D; // Initialize
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
StartDate := 0D; // Initialize
|
||||
```
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
+++
|
||||
title = "Spacing Brackets and ::"
|
||||
weight = 1130
|
||||
+++
|
||||
There must be no spaces characters before and after [] dimension brackets symbols or :: option symbols.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
A[i] [j] := Amt;
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
A[i][j] := Amt;
|
||||
```
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
"Currency Exchange Rate"."Fix Exchange Rate Amount" :: Currency:
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
"Currency Exchange Rate"."Fix Exchange Rate Amount"::Currency:
|
||||
```
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF FIND (Which) THEN
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF FIND(Which) THEN
|
||||
```
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
+++
|
||||
title = "Spacing Unary Operators"
|
||||
weight = 1140
|
||||
+++
|
||||
There must be no space between a unary operator and its argument (except for the NOT keyword).
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF NOT(Type = Type::Item) THEN
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF NOT (Type = Type::Item) THEN
|
||||
```
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
DiscAmt := - "Discount Amount";
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
DiscAmt := -"Discount Amount";
|
||||
```
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,31 @@
|
|||
+++
|
||||
title = "Temporary Variable Naming"
|
||||
weight = 1200
|
||||
+++
|
||||
The name of a temporary variable must be prefixed with the word Temp and not otherwise.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
JobWIPBuffer@1002 : TEMPORARY Record 1018;
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
TempJobWIPBuffer@1002 : TEMPORARY Record 1018;
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
TempJobWIPBuffer@1002 : Record 1018;
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
CopyOfJobWIPBuffer@1002 : Record 1018;
|
||||
```
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
+++
|
||||
title = "TextConst Suffixes"
|
||||
weight = 1210
|
||||
+++
|
||||
TextConst variable names should have a suffix (an approved three-letter suffix: Msg, Tok, Err, Qst, Lbl, Txt) describing usage.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
CannotDeleteLine@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.';
|
||||
...
|
||||
ERROR(CannotDeleteLine,TABLECAPTION);
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
CannotDeleteLineErr@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.';
|
||||
...
|
||||
ERROR(CannotDeleteLineErr,TABLECAPTION);
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
Text000@1011 : TextConst 'ENU="has been changed (initial a %1: %2= %3, %4= %5)"';
|
||||
...
|
||||
SalesLine.FIELDERROR(Type,STRSUBSTNO(Text000,...);
|
||||
...
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
TypeHasBeenChangedErr@1011 : TextConst 'ENU="has been changed (initial a %1: %2= %3, %4= %5)"';
|
||||
...
|
||||
SalesLine.FIELDERROR(Type,STRSUBSTNO(TypeHasBeenChangedErr,...);
|
||||
...
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
Text004@1004 : TextConst 'ENU=Indenting the Job Tasks \#1\#\#\#\#\#\#\#\#\#\#.';
|
||||
...
|
||||
Window@1007 : Dialog;
|
||||
...
|
||||
Window.OPEN(Text004);
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IndentingMsg@1004 : TextConst 'ENU=Indenting the Job Tasks \#1\#\#\#\#\#\#\#\#\#\#.';
|
||||
...
|
||||
Window@1007 : Dialog;
|
||||
...
|
||||
Window.OPEN(IndentingMsg);
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
Text002@1005 : TextConst 'ENU=You cannot delete a %1 that is used in one or more setup windows.\\ Do you want to open the G/L Account No. Where-Used List Window?';
|
||||
...
|
||||
IF CONFIRM(Text002,TRUE,GLAcc.TABLECAPTION) THEN
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
OpenWhereUsedWindowQst@1005 : TextConst 'ENU=You cannot delete a %1 that is used in one or more setup windows.\\ Do you want to open the G/L Account No. Where-Used List Window?';
|
||||
...
|
||||
IF CONFIRM(OpenWhereUsedWindowQst,TRUE,GLAcc.TABLECAPTION) THEN
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
Selection := STRMENU(Text003,2);
|
||||
...
|
||||
Text003@1002 : TextConst 'ENU=&Copy dimensions from BOM,&Retrieve dimensions from components';
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
Selection := STRMENU(CopyFromQst,2);
|
||||
...
|
||||
CopyFromQst@1002 : TextConst 'ENU=&Copy dimensions from BOM,&Retrieve dimensions from components';
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
DATASET
|
||||
{
|
||||
...
|
||||
{ 1 ;1 ;Column ;Chart_of_AccountsCaption;
|
||||
SourceExpr=Chart_of_AccountsCaption }
|
||||
...
|
||||
Chart_of_AccountsCaption@9647 : TextConst 'ENU=Chart of Accounts';
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
DATASET
|
||||
{
|
||||
...
|
||||
{ 1 ;1 ;Column ;Chart_of_AccountsCaption;
|
||||
SourceExpr=ChartOfAccountsLbl }
|
||||
...
|
||||
ChartOfAccountsLbl@9647 : TextConst 'ENU=Chart of Accounts';
|
||||
```
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
+++
|
||||
title = "Unary Operator Line End"
|
||||
weight = 1250
|
||||
+++
|
||||
Do not end a line with unary operator.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
"Quantity Handled (Base)" := -
|
||||
"Quantity Handled (Base)");
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
"Quantity Handled (Base)" :=
|
||||
- "Quantity Handled (Base)");
|
||||
```
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
+++
|
||||
title = "Unnecessary Compound Parenthesis"
|
||||
weight = 1260
|
||||
+++
|
||||
Use parenthesis only to enclose compound expressions inside compound expressions.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF ("Costing Method" = "Costing Method"::Standard) THEN
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF "Costing Method" = "Costing Method"::Standard THEN
|
||||
```
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
ProfitPct = -(Profit) / CostAmt * 100;
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
ProfitPct = -Profit / CostAmt * 100;
|
||||
```
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
+++
|
||||
title = "Unnecessary ELSE"
|
||||
weight = 1270
|
||||
+++
|
||||
ELSE should not be used when the last action in the THEN part is an EXIT, BREAK, SKIP, QUIT, ERROR.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF IsAdjmtBinCodeChanged THEN
|
||||
ERROR(AdjmtBinCodeChangeNotAllowedErr,...)
|
||||
ELSE
|
||||
ERROR(BinCodeChangeNotAllowedErr,...);
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF IsAdjmtBinCodeChanged THEN
|
||||
ERROR(AdjmtBinCodeChangeNotAllowedErr,...)
|
||||
ERROR(BinCodeChangeNotAllowedErr,...);
|
||||
```
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
+++
|
||||
title = "Unnecessary Function Parenthesis"
|
||||
weight = 1280
|
||||
+++
|
||||
Do not use parenthesis in a function call if the function does not have any parameters.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF ReservMgt.IsPositive() THEN
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF ReservMgt.IsPositive THEN
|
||||
```
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF ChangeStatusForm.RUNMODAL() <> ACTION::Yes THEN
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF ChangeStatusForm.RUNMODAL <> ACTION::Yes THEN
|
||||
```
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
+++
|
||||
title = "Unnecessary Separators"
|
||||
weight = 1290
|
||||
+++
|
||||
There should be no unnecessary separators.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF Customer.FINDFIRST THEN;;
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF Customer.FINDFIRST THEN;
|
||||
```
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
+++
|
||||
title = "Unnecessary TRUE/FALSE"
|
||||
weight = 1300
|
||||
+++
|
||||
Do not use TRUE or FALSE keywords unnecessarily if the expression is already an logical expression.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
IF IsPositive() = TRUE THEN
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF IsPositive THEN
|
||||
```
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
```
|
||||
IF Complete <> TRUE THEN
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
IF NOT Complete THEN
|
||||
```
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
+++
|
||||
title = "Variable Already Scoped"
|
||||
weight = 1400
|
||||
+++
|
||||
Do not use scope ''.'' qualifier unnecessarily when a variable is already implicitly or explicitly scoped. It keeps the code simpler.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
ReturnRcptHeader.SETRANGE(ReturnRcptHeader."Return Order No.","Document No.");
|
||||
```
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
ReturnRcptHeader.SETRANGE("Return Order No.","Document No.");
|
||||
```
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
WITH ChangeLogSetupTable DO BEGIN
|
||||
...
|
||||
IF ChangeLogSetupTable.DELETE THEN
|
||||
...
|
||||
END;
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
WITH ChangeLogSetupTable DO BEGIN
|
||||
...
|
||||
IF DELETE THEN
|
||||
...
|
||||
END;
|
||||
```
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
+++
|
||||
title = "Variable Naming"
|
||||
weight = 1420
|
||||
+++
|
||||
Variables that refer to a C/AL object must contain the objects name, abbreviated where necessary.
|
||||
|
||||
A variable must begin with a capital letter.
|
||||
|
||||
Blanks, periods, and other characters (such as parentheses) that would make quotation marks around a variable necessary must be omitted.
|
||||
|
||||
If a variable is a compound of two or more words or abbreviations, each word or abbreviation should begin with a capital letter.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
...
|
||||
WIPBuffer@1002 : Record 1018
|
||||
...
|
||||
OBJECT Table Job WIP Buffer
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
...
|
||||
JobWIPBuffer@1002 : Record 1018
|
||||
...
|
||||
OBJECT Table Job WIP Buffer
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
...
|
||||
Postline@1004 : Codeunit 12;
|
||||
...
|
||||
OBJECT Codeunit Gen. Jnl.-Post Line
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
...
|
||||
GenJnlPostLine@1004 : Codeunit 12;
|
||||
...
|
||||
OBJECT Codeunit Gen. Jnl.-Post Line
|
||||
```
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
LOCAL PROCEDURE HandleCustDebitCredit@17(...;"Amount (LCY)"@1001 : Decimal;...);
|
||||
BEGIN
|
||||
IF ((... ("Amount (LCY)" \> 0)) ...) OR
|
||||
((... ("Amount (LCY)" < 0)) ...)
|
||||
THEN BEGIN
|
||||
...
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
LOCAL PROCEDURE HandleCustDebitCredit@17(...;AmountLCY@1001 : Decimal;...);
|
||||
BEGIN
|
||||
IF ((... (AmountLCY \> 0)) ...) OR
|
||||
((... (AmountLCY < 0)) ...)
|
||||
THEN BEGIN
|
||||
...
|
||||
```
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
+++
|
||||
title = "Variables Declarations Order"
|
||||
weight = 1430
|
||||
+++
|
||||
Variables declarations should be ordered by type. In general, object and complex variable types are listed first followed by simple variables. The order should be the same as the object list in the object designer for C/AL objects. Afterwards come the complex variables like RecordRef, .NET, FieldRef etc. At the end come all the simple data types in no particular order.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
StartingDateFilter@1002 : Text[30];
|
||||
Vend@1003 : Record 23;
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
Vend@1003 : Record 23;
|
||||
StartingDateFilter@1002 : Text[30];
|
||||
```
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
+++
|
||||
title = "UX"
|
||||
weight = 1390
|
||||
+++
|
||||
## C/AL Coding Guidelines
|
||||
|
||||
## **User eXperience**
|
||||
|
||||
Find the C/AL guidelines by expanding the menu in the left.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
+++
|
||||
title = "Actions - Images"
|
||||
weight = 200
|
||||
+++
|
||||
All actions must have an image assigned to them.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
{ 7 ;1 ;Action ;
|
||||
CaptionML=ENU=Customer - &Balance;
|
||||
RunObject=Report 121 }
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
{ 7 ;1 ;Action ;
|
||||
CaptionML=ENU=Customer - &Balance;
|
||||
RunObject=Report 121 }
|
||||
Image=Report }
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
+++
|
||||
title = "CONFIRM"
|
||||
weight = 380
|
||||
+++
|
||||
Always end CONFIRM with a question mark.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
ChangeAllOpenedEntriesQst@1000 : TextConst 'ENU=Do you want to change all open entries for every customer and vendor that are not blocked';
|
||||
...
|
||||
IF CONFIRM(ChangeAllOpenedEntriesQst,TRUE) THEN
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
ChangeAllOpenedEntriesQst@1000 : TextConst 'ENU=Do you want to change all open entries for every customer and vendor that are not blocked?';
|
||||
...
|
||||
IF CONFIRM(ChangeAllOpenedEntriesQst,TRUE) THEN
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
+++
|
||||
title = "FIELDERROR"
|
||||
weight = 590
|
||||
+++
|
||||
Never use FIELDERROR with a period as it is automatically inserted.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
InvalidValue@1025 : TextConst 'ENU=is invalid.';
|
||||
...
|
||||
Cust.FIELDERROR("No.",InvalidValue);
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
InvalidValue@1025 : TextConst 'ENU=is invalid';
|
||||
...
|
||||
Cust.FIELDERROR("No.",InvalidValue);
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
+++
|
||||
title = "MESSAGE and ERROR"
|
||||
weight = 790
|
||||
+++
|
||||
Always end MESSAGE or ERROR with a period.
|
||||
|
||||
Bad code
|
||||
|
||||
```al
|
||||
CustIsBlockedErr@1025 : TextConst 'ENU=You cannot %1 this type of document when Customer %2 is blocked with type %3';
|
||||
...
|
||||
ERROR(CustIsBlockedErr,...);
|
||||
```
|
||||
|
||||
Good code
|
||||
|
||||
```al
|
||||
CustIsBlockedErr@1025 : TextConst 'ENU=You cannot %1 this type of document when Customer %2 is blocked with type %3.';
|
||||
...
|
||||
ERROR(CustIsBlockedErr,...);
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue