Restructure for Docsy theme
This commit is contained in:
parent
f4a1ebc077
commit
cceb0c945e
376 changed files with 963 additions and 186 deletions
35
content/docs/BestPractices/DeleteAll/index.md
Normal file
35
content/docs/BestPractices/DeleteAll/index.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
title: "DeleteAll"
|
||||
tags: ["Performance"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
<_Created by waldo, Described by waldo_\>
|
||||
|
||||
## Description
|
||||
|
||||
When you perform a "DeleteAll" when there is nothing to delete, it will still perform a lock. When you for example perform a DeleteAll on an empty table, it will result in a table lock.
|
||||
Therefore it's good practice to always check if the table is empty when performing a DeleteAll.
|
||||
|
||||
## Bad code
|
||||
|
||||
```al
|
||||
EmptyTableWLD.SetRange(Code, 'AJ');
|
||||
EmptyTableWLD.DeleteAll(true);
|
||||
```
|
||||
|
||||
## Good code
|
||||
|
||||
```al
|
||||
EmptyTableWLD.SetRange(Code, 'AJ');
|
||||
if not EmptyTableWLD.IsEmpty() then
|
||||
EmptyTableWLD.DeleteAll(true);
|
||||
```
|
||||
|
||||
## Discussions
|
||||
|
||||
You can discuss the guideline [here](https://github.com/microsoft/alguidelines/discussions/107)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
197
content/docs/BestPractices/SubscriberCodeunits/index.md
Normal file
197
content/docs/BestPractices/SubscriberCodeunits/index.md
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
---
|
||||
title: "Subscriber Codeunits"
|
||||
tags: ["Performance"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by waldo, Described by waldo_
|
||||
|
||||
## Description
|
||||
|
||||
In general, subscribers have to be put in codeunits. There are a few performance considerations that you should keep in the back of your minds, when designing such a codeunit.
|
||||
|
||||
- Keep the codeunit as small as possible
|
||||
- Work with a single instance codeunit
|
||||
- only subscribe when necessary
|
||||
- Avoid generic OnInsert/OnModify/OnDelete
|
||||
|
||||
Let's discuss all points
|
||||
|
||||
## Keep the codeunit as small as possible
|
||||
Every time a subscriber gets called, a new instance of the codeunit is being loaded in memory, which takes memory and processing power. The smaller the codeunit, the less memory, and the faster it is.
|
||||
|
||||
Therefore, it's suggested to split the subscribers by functionality and avoid putting business logic in the actual codeunit. Tip: put all business logic in an "[Method Codeunit](https://alguidelines.dev/bcpatterns/generic-method-pattern/)".
|
||||
|
||||
Examples:
|
||||
- if you app does things on Sales and Purchase, create a Sales-subs codeunit, and a Purchase-subs.
|
||||
- if you have multiple functionalities in your app (let's call'm modules), create a subs-codeunit per module, and only add the subscribers in there that are necessary for that module.
|
||||
|
||||
### Bad code
|
||||
```AL
|
||||
codeunit 2037325 "Setup Subs"
|
||||
{
|
||||
SingleInstance = true;
|
||||
|
||||
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
|
||||
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
|
||||
var
|
||||
AppId: ModuleInfo;
|
||||
NameListLbl: Label 'Linked Texts Framework - List', Locked = true;
|
||||
DescriptionListLbl: Label 'Edit linked texts', Locked = true;
|
||||
KeyWordListLbl: Label 'LT,Distri,Technical,Functional,Reports', Locked = true;
|
||||
NameReportLbl: Label 'Linked Texts Framework - Reports', Locked = true;
|
||||
DescriptionReportLbl: Label 'View linked texts reports', Locked = true;
|
||||
KeyWordReportLbl: Label 'LT,Distri,Technical,Functional,Reports', Locked = true;
|
||||
begin
|
||||
navapp.GetCurrentModuleInfo(AppId);
|
||||
Sender.Insert(NameListLbl, DescriptionListLbl, KeyWordListLbl, page::"LTE Linked Text List", AppId.Id(), "Manual Setup Category"::General);
|
||||
Sender.Insert(NameReportLbl, DescriptionReportLbl, KeyWordReportLbl, page::"LTE Linked Texts Reports", AppId.Id(), "Manual Setup Category"::General);
|
||||
end;
|
||||
|
||||
[EventSubscriber(ObjectType::Codeunit, codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
|
||||
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
|
||||
var
|
||||
AppId: ModuleInfo;
|
||||
NameLayoutLbl: Label 'Report Helper - Layout', Locked = true;
|
||||
DescriptionLayoutLbl: Label 'Set up or update report layout list', Locked = true;
|
||||
KeyWordLayoutLbl: Label 'RH,Distri,Technical,Functional,Reports,Layout', Locked = true;
|
||||
NameCaptionsLbl: Label 'Report Helper - Captions', Locked = true;
|
||||
DescriptionCaptionsLbl: Label 'Set up or update captions list', Locked = true;
|
||||
KeyWordCaptionsLbl: Label 'RH,Distri,Technical,Functional,Reports,Captions', Locked = true;
|
||||
NameFunctionsLbl: Label 'Report Helper - Functions', Locked = true;
|
||||
DescriptionFunctionsLbl: Label 'Set up or disable functions', Locked = true;
|
||||
KeyWordFunctionsLbl: Label 'RH,Distri,Technical,Functional,Reports,Functions', Locked = true;
|
||||
NameDFCLbl: Label 'Report Helper - Default Footer', Locked = true;
|
||||
DescriptionDFCLbl: Label 'Set up or update default footer', Locked = true;
|
||||
KeyWordDFCLbl: Label 'RH,Distri,Technical,Functional,Reports,Default,Footer', Locked = true;
|
||||
begin
|
||||
navapp.GetCurrentModuleInfo(AppId);
|
||||
Sender.Insert(NameLayoutLbl, DescriptionLayoutLbl, KeyWordLayoutLbl, page::"RHE Report Layout List", AppId.Id(), "Manual Setup Category"::General);
|
||||
Sender.Insert(NameCaptionsLbl, DescriptionCaptionsLbl, KeyWordCaptionsLbl, page::"RHE Captions", AppId.Id(), "Manual Setup Category"::General);
|
||||
Sender.Insert(NameFunctionsLbl, DescriptionFunctionsLbl, KeyWordFunctionsLbl, page::"RHE Functions", AppId.Id(), "Manual Setup Category"::General);
|
||||
Sender.Insert(NameDFCLbl, DescriptionDFCLbl, KeyWordDFCLbl, page::"RHE Default Footer Card", AppId.Id(), "Manual Setup Category"::General);
|
||||
end;
|
||||
}
|
||||
```
|
||||
### Good code
|
||||
|
||||
Split into 2 codeunits, and move the business logic out.
|
||||
|
||||
```AL
|
||||
codeunit 2037325 "LTE Setup Subs"
|
||||
{
|
||||
SingleInstance = true;
|
||||
|
||||
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
|
||||
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
|
||||
var
|
||||
RegisterLTEManualSetup: codeunit "Register LTE Manual Setup";
|
||||
begin
|
||||
RegisterLTEManualSetup.RegisterLTEManualSetup();
|
||||
end;
|
||||
}
|
||||
|
||||
codeunit 2037324 "RHE Setup Subs"
|
||||
{
|
||||
SingleInstance = true;
|
||||
|
||||
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
|
||||
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
|
||||
var
|
||||
RegisterRHEManualSetup: codeunit "Register RHE Manual Setup";
|
||||
begin
|
||||
RegisterRHEManualSetup.RegisterRHEManualSetup();
|
||||
end;
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Work with a single instance codeunit
|
||||
|
||||
To avoid the extra "loading of the content" while a subscriber is being executed, use Single Instance codeunit for subscribers. Do take into account, of course, that it would share the state across the entire session.
|
||||
|
||||
### Bad code
|
||||
```AL
|
||||
codeunit 2037324 "RHE Setup Subs"
|
||||
{
|
||||
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
|
||||
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
|
||||
var
|
||||
RegisterRHEManualSetup: codeunit "Register RHE Manual Setup";
|
||||
begin
|
||||
RegisterRHEManualSetup.RegisterRHEManualSetup();
|
||||
end;
|
||||
}
|
||||
```
|
||||
### Good code
|
||||
```AL
|
||||
codeunit 2037324 "RHE Setup Subs"
|
||||
{
|
||||
SingleInstance = true;
|
||||
|
||||
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
|
||||
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
|
||||
var
|
||||
RegisterRHEManualSetup: codeunit "Register RHE Manual Setup";
|
||||
begin
|
||||
RegisterRHEManualSetup.RegisterRHEManualSetup();
|
||||
end;
|
||||
}
|
||||
```
|
||||
|
||||
## only subscribe when necessary
|
||||
|
||||
If possible, only execute the subscriber when really necessary by using Manual Binding.
|
||||
|
||||
### Bad code
|
||||
```AL
|
||||
//subscriber - code should actually only run when Color=Red.
|
||||
[EventSubscriber(ObjectType::Table, Database::"Just Some Table WLD", 'OnAfterValidateEvent', 'Message 2', false, false)]
|
||||
local procedure JustDoSomthing(var Rec: Record "Just Some Table WLD"; var xRec: Record "Just Some Table WLD")
|
||||
begin
|
||||
if Rec.color <> 'RED' then
|
||||
exit; //only execute when necessary
|
||||
|
||||
...
|
||||
end;
|
||||
|
||||
//business logic
|
||||
if JustSomeTable.FindSet() then
|
||||
repeat
|
||||
JustSomeTable.Validate("Message 2", format(Random(1000)));
|
||||
until JustSomeTable.Next() < 1;
|
||||
```
|
||||
### Good code
|
||||
```AL
|
||||
if JustSomeTable.FindSet() then
|
||||
repeat
|
||||
if JustSomeTable.Color = 'RED' then
|
||||
BindSubscription(DemoSubs);
|
||||
|
||||
JustSomeTable.Validate("Message 2", format(Random(1000)));
|
||||
|
||||
if JustSomeTable.Color = 'RED' then
|
||||
UnbindSubscription(DemoSubs);
|
||||
until JustSomeTable.Next() < 1;
|
||||
```
|
||||
|
||||
## Avoid OnInsert/OnModify/OnDelete
|
||||
The reason for this is, that it breaks the batch-calls:
|
||||
- Any "OnInsert" subscriber breaks the bulk inserts, simply because it needs to perform an operation after every record that was inserted
|
||||
- Any "OnModify" subscriber slows down the "ModifyAll", simply because it needs to perform an operation after every record that was modified. I fact: 1 SQL call is turned into a loop of SQL calls.
|
||||
- Any "OnDelete" subscriber slows down the "DeleteAll", simply because it needs to perform an operation after every record that was deleted. I fact: 1 SQL call is turned into a loop of SQL calls.
|
||||
|
||||
Avoid subscribers to these events.
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/92)
|
||||
|
||||
You can discuss this guidelines [here](https://github.com/microsoft/alguidelines/discussions/92).
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
|
||||
## References
|
||||
|
||||
The [Generic Method Pattern](https://alguidelines.dev/bcpatterns/generic-method-pattern/)
|
||||
21
content/docs/BestPractices/_index.md
Normal file
21
content/docs/BestPractices/_index.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
title: "Best Practices"
|
||||
weight: 3
|
||||
description: >
|
||||
AL Code Best Practices
|
||||
---
|
||||
|
||||
# Business Central Best Practices
|
||||
|
||||
This section will be cover things that aren't as simple as Design Patterns, but will help make sure your development is:
|
||||
- high-performance
|
||||
- complies with good designs
|
||||
- has high maintainability
|
||||
|
||||
## Readability
|
||||
|
||||
Generally, all readability rules are Microsoft style choices only. You can use them to keep consistency with the existing code.
|
||||
|
||||
## Performance
|
||||
|
||||
Some guidelines are simply better for performance considerations rather than readability or anything else. In this section, let's look into some..
|
||||
34
content/docs/BestPractices/begin-as-an-afterword/index.md
Normal file
34
content/docs/BestPractices/begin-as-an-afterword/index.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
title: "begin as an afterword"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
|
||||
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
|
||||
|
||||
```al
|
||||
if ICPartnerRefType = ICPartnerRefType::"Common Item No." then begin
|
||||
...
|
||||
end;
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=begin+as+an+After+Word+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
60
content/docs/BestPractices/begin-end/index.md
Normal file
60
content/docs/BestPractices/begin-end/index.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
title: "Begin-End - Compound Only"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
Only use begin..end to enclose [compound statements](https://docs.microsoft.com/en-us/cpp/c-language/compound-statement-c?view=msvc-170#:~:text=A%20compound%20statement%20%28also%20called%20a%20%22block%22%29%20typically,appear%20at%20the%20head%20of%20a%20compound%20statement.).
|
||||
|
||||
## 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);
|
||||
```
|
||||
|
||||
## Exception
|
||||
|
||||
```AL
|
||||
// Except for this case
|
||||
if X then begin
|
||||
if Y then
|
||||
//DO SOMETHING;
|
||||
end else
|
||||
(not X)
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=begin+end+compound+only+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
title: "Binary Operator to Start Line"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
|
||||
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"
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=binary+operator+to+start+line+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
37
content/docs/BestPractices/case-actions/index.md
Normal file
37
content/docs/BestPractices/case-actions/index.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
title: "CASE Action on next line"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
|
||||
A CASE action should start on a line after the possibility.
|
||||
|
||||
## Bad code
|
||||
|
||||
```AL
|
||||
case Letter of
|
||||
'A': Letter2 := '10';
|
||||
'B': Letter2 := '11';
|
||||
end;
|
||||
```
|
||||
|
||||
## Good code
|
||||
|
||||
```AL
|
||||
case Letter of
|
||||
'A':
|
||||
Letter2 := '10';
|
||||
'B':
|
||||
Letter2 := '11';
|
||||
end;
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=case+action+on+next+line+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
29
content/docs/BestPractices/comments-spacing/index.md
Normal file
29
content/docs/BestPractices/comments-spacing/index.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: "Comment Spacing"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
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
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=comment+spacing+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
41
content/docs/BestPractices/end-else-pair/index.md
Normal file
41
content/docs/BestPractices/end-else-pair/index.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: "end else pair"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
|
||||
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
|
||||
begin
|
||||
...
|
||||
end;
|
||||
```
|
||||
|
||||
## Good code
|
||||
|
||||
```al
|
||||
if OppEntry.Find('-') then
|
||||
if SalesCycleStage.Find('-') then begin
|
||||
...
|
||||
end else begin
|
||||
...
|
||||
end;
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=end+else/pair+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
title: "Keyword Pairs - Indentation"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
The `if..then` pair, `while..do` pair, and `for..do` pair must appear on the same line or the same level of indentation. If possible, you can align the lines it is even much more readable.
|
||||
|
||||
## Bad code
|
||||
|
||||
```al
|
||||
if (x = y) and
|
||||
(a = b) then
|
||||
```
|
||||
|
||||
## Good code
|
||||
|
||||
```al
|
||||
if (x = y) and
|
||||
(a = b)
|
||||
then
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=keyword+pair+indentation+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
37
content/docs/BestPractices/line-start-keywords/index.md
Normal file
37
content/docs/BestPractices/line-start-keywords/index.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
title: "Line Start Keywords"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
<_Created by Microsoft, Described by waldo_\>
|
||||
|
||||
## Description
|
||||
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();
|
||||
```
|
||||
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=line+start+keyword+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
29
content/docs/BestPractices/lonely-repeat/index.md
Normal file
29
content/docs/BestPractices/lonely-repeat/index.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: "Lonely Repeat"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
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
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=lonely+repeat+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
28
content/docs/BestPractices/named-invocations/index.md
Normal file
28
content/docs/BestPractices/named-invocations/index.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
title: "Named Invocations"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
When calling an object statically use the Object Name, not the Object Id.
|
||||
|
||||
## Bad code
|
||||
|
||||
```al
|
||||
Page.RunModal(525, SalesShptLine);
|
||||
```
|
||||
|
||||
## Good code
|
||||
|
||||
```al
|
||||
Page.RunModal(Page::"Posted Sales Shipment Lines", SalesShptLine);
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=named+invocations+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
44
content/docs/BestPractices/one-statement-per-line/index.md
Normal file
44
content/docs/BestPractices/one-statement-per-line/index.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
title: "One Statement per Line"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
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;
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=one+statement+per+line+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
35
content/docs/BestPractices/separate-if-and-else/index.md
Normal file
35
content/docs/BestPractices/separate-if-and-else/index.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
title: "Seperate if and else"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
`if` and `else` statements should be on separate lines.
|
||||
|
||||
## Bad code
|
||||
|
||||
```al
|
||||
if Atom = '\>' then HasLogicalOperator := true else begin
|
||||
...
|
||||
end;
|
||||
```
|
||||
|
||||
## Good code
|
||||
|
||||
```al
|
||||
if Atom = '\>' then
|
||||
HasLogicalOperator := true
|
||||
else begin
|
||||
...
|
||||
end;
|
||||
```
|
||||
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=separate+if+and+else+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
46
content/docs/BestPractices/spacing-binary-operators/index.md
Normal file
46
content/docs/BestPractices/spacing-binary-operators/index.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: "Spacing Binary Operators"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
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 a space after the comma.
|
||||
|
||||
## 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
|
||||
```
|
||||
367
content/docs/BestPractices/suggested-abbreviations/index.md
Normal file
367
content/docs/BestPractices/suggested-abbreviations/index.md
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
---
|
||||
title: "Suggested Abbreviations"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
|
||||
Whenever possible, do **not** use abbreviations in variables, functions and objects names.
|
||||
|
||||
If there is no other choice, then use the suggestions below.
|
||||
|
||||
| word | Abbreviation |
|
||||
|---|---|
|
||||
| absence | Abs |
|
||||
| account | Acc |
|
||||
| accounting | Acc |
|
||||
| accumulated | Accum |
|
||||
| action | Act |
|
||||
| activity | Activ |
|
||||
| additional | Add |
|
||||
| address | Addr |
|
||||
| adjust | Adj |
|
||||
| adjusted | Adjd |
|
||||
| adjustment | Adjmt |
|
||||
| agreement | Agrmt |
|
||||
| allocation | Alloc |
|
||||
| allowance | Allow |
|
||||
| alternative | Alt |
|
||||
| amount | Amt |
|
||||
| amounts | Amts |
|
||||
| answer | Ans |
|
||||
| applies | Appl |
|
||||
| application | Appln |
|
||||
| arrival | Arriv |
|
||||
| assembly | Asm |
|
||||
| assemble to order | ATO |
|
||||
| assignment | Assgnt |
|
||||
| associated | Assoc |
|
||||
| attachment | Attmt |
|
||||
| authorities | Auth |
|
||||
| automatic | Auto |
|
||||
| availability | Avail |
|
||||
| average | Avg |
|
||||
| ba db. | BA |
|
||||
| balance | Bal |
|
||||
| bill of materials | BOM |
|
||||
| blanket | Blnkt |
|
||||
| budget | Budg |
|
||||
| buffer | Buf |
|
||||
| business | Bus |
|
||||
| business interaction management | BIM |
|
||||
| buying | Buy |
|
||||
| calculate | Calc |
|
||||
| calculated | Calcd |
|
||||
| calculation | Calcu |
|
||||
| calendar | Cal |
|
||||
| capacity | Cap |
|
||||
| capacity requirements planning | CRP |
|
||||
| cash flow | CF |
|
||||
| cashflow | CF |
|
||||
| catalog | ctlg |
|
||||
| category | Cat |
|
||||
| Central Processing Unit | CPU |
|
||||
| center | Ctr |
|
||||
| change | Chg |
|
||||
| changes | Chgs |
|
||||
| character | Char |
|
||||
| characters | Chars |
|
||||
| charge | Chrg |
|
||||
| charges | Chrgs |
|
||||
| check | Chk |
|
||||
| classification | Class |
|
||||
| collection | coll |
|
||||
| column | col |
|
||||
| comment | Cmt |
|
||||
| company | Co |
|
||||
| component | Comp |
|
||||
| completion | Cmpltn |
|
||||
| components | Comps |
|
||||
| composition | Compn |
|
||||
| compression | Compr |
|
||||
| concurrent | Concrnt |
|
||||
| confidential | Conf |
|
||||
| confirmation | Cnfrmn |
|
||||
| conflict | Confl |
|
||||
| consolidate | Consol |
|
||||
| consolidation | Consolid |
|
||||
| consumption | Consump |
|
||||
| contact | Cont |
|
||||
| container | Cntr |
|
||||
| contract | Contr |
|
||||
| contracted | Contrd |
|
||||
| control | Ctrl |
|
||||
| controls | Ctrls |
|
||||
| conversion | Conv |
|
||||
| correction | Cor |
|
||||
| correspondence | Corres |
|
||||
| corresponding | Corresp |
|
||||
| cost | Cst |
|
||||
| sold | COGS |
|
||||
| credit | Cr |
|
||||
| cumulate | Cumul |
|
||||
| currency | Curr |
|
||||
| current | Crnt |
|
||||
| customer | Cust |
|
||||
| customer/vendor | CV |
|
||||
| daily | Dly |
|
||||
| dampener | Damp |
|
||||
| database management system | DBMS |
|
||||
| date | D |
|
||||
| definition | Def |
|
||||
| demonstration | Demo |
|
||||
| department | Dept |
|
||||
| department/project | DP |
|
||||
| depreciation | Depr |
|
||||
| description | Desc |
|
||||
| detail | Dtl |
|
||||
| detailed | Dtld |
|
||||
| details | Dtls |
|
||||
| deviation | Dev |
|
||||
| difference | Diff |
|
||||
| dimension | Dim |
|
||||
| direct | Dir |
|
||||
| discount | Disc |
|
||||
| discrete | Discr |
|
||||
| distribute | Distr |
|
||||
| distributed | Distrd |
|
||||
| distributor | Distbtr |
|
||||
| distribution | Distrn |
|
||||
| document | Doc |
|
||||
| duplicate | Dupl |
|
||||
| entered | Entrd |
|
||||
| engineering | Engin |
|
||||
| exchange | Exch |
|
||||
| excluding | Excl |
|
||||
| execute | Exec |
|
||||
| expected | Expd |
|
||||
| expedited | Exped |
|
||||
| expense | Exp |
|
||||
| expression | Expr |
|
||||
| expiration | Expir |
|
||||
| extended | Ext |
|
||||
| explode | Expl |
|
||||
| export | Expt |
|
||||
| final | Fnl |
|
||||
| finance | Fin |
|
||||
| fiscal | Fisc |
|
||||
| finished | Fnshd |
|
||||
| fixed asset | FA |
|
||||
| forward | Fwd |
|
||||
| freight | Frt |
|
||||
| general | Gen |
|
||||
| general ledger | GL |
|
||||
| group | Gr |
|
||||
| header | Hdr |
|
||||
| history | Hist |
|
||||
| holiday | Hol |
|
||||
| human resource | HR |
|
||||
| identification | ID |
|
||||
| import | Imp |
|
||||
| inbound | Inbnd |
|
||||
| including | Incl |
|
||||
| included | Incld |
|
||||
| incoming | Incmg |
|
||||
| independent software vendor | ISV |
|
||||
| industry | Indust |
|
||||
| information | Info |
|
||||
| initial | Init |
|
||||
| Intrastat | Intra |
|
||||
| interaction | Interact |
|
||||
| integration | Integr |
|
||||
| interest | Int |
|
||||
| Interim | Intm |
|
||||
| internal protocol | IP |
|
||||
| inventory | Invt |
|
||||
| inventoriable | Invtbl |
|
||||
| invoice | Inv |
|
||||
| invoiced | Invd |
|
||||
| item tracking | IT |
|
||||
| journal | Jnl |
|
||||
| language | Lang |
|
||||
| ledger | Ledg |
|
||||
| level | Lvl |
|
||||
| line | Ln |
|
||||
| list | Lt |
|
||||
| local currency | LCY |
|
||||
| location | Loc |
|
||||
| mailing | Mail |
|
||||
| maintenance | Maint |
|
||||
| management | Mgt |
|
||||
| manual | Man |
|
||||
| manufacturing | Mfg |
|
||||
| manufacturer | Mfr |
|
||||
| material | Mat |
|
||||
| marketing | Mktg |
|
||||
| maximum | Max |
|
||||
| measure | Meas |
|
||||
| message | Msg |
|
||||
| minimum | Min |
|
||||
| miscellaneous | Misc |
|
||||
| modify | Mod |
|
||||
| month | Mth |
|
||||
| negative | Neg |
|
||||
| non-inventoriable | NonInvtbl |
|
||||
| notification | Notif |
|
||||
| number | No |
|
||||
| numbers | Nos |
|
||||
| object | Obj |
|
||||
| operating | Oper |
|
||||
| opportunity | Opp |
|
||||
| order | Ord |
|
||||
| orders | Ords |
|
||||
| original | Orig |
|
||||
| organization | Org |
|
||||
| outbound | Outbnd |
|
||||
| Outgoing | Outg |
|
||||
| output | Out |
|
||||
| outstanding | Outstd |
|
||||
| overhead | Ovhd |
|
||||
| payment | Pmt |
|
||||
| percent | Pct |
|
||||
| personnel | Persnl |
|
||||
| physical | Phys |
|
||||
| picture | Pic |
|
||||
| planning | Plng |
|
||||
| posted | Pstd |
|
||||
| posting | Post |
|
||||
| positive | Pos |
|
||||
| precision | Prec |
|
||||
| prepayment | Prepmt |
|
||||
| product | Prod |
|
||||
| production | Prod |
|
||||
| production order | ProdOrd |
|
||||
| project | Proj |
|
||||
| property | Prop |
|
||||
| prospect | Prspct |
|
||||
| purchase | Purch |
|
||||
| purchases | Purch |
|
||||
| purchaser | Purchr |
|
||||
| purchase order | PurchOrd |
|
||||
| quality | Qlty |
|
||||
| quantity | Qty |
|
||||
| questionnaire | Questn |
|
||||
| quote | Qte |
|
||||
| radio frequency | RF |
|
||||
| range | Rng |
|
||||
| receipt | Rcpt |
|
||||
| received | Rcd |
|
||||
| record | Rec |
|
||||
| records | Recs |
|
||||
| reconcile | Recncl |
|
||||
| reconciliation | Recon |
|
||||
| recurring | Recur |
|
||||
| reference | Ref |
|
||||
| register | Reg |
|
||||
| registration | Regn |
|
||||
| registered | Regd |
|
||||
| relation | Rel |
|
||||
| relations | Rels |
|
||||
| relationship | Rlshp |
|
||||
| release | Rlse |
|
||||
| released | Rlsd |
|
||||
| remaining | Rem |
|
||||
| reminder | Rmdr |
|
||||
| replacement | Repl |
|
||||
| replenish | Rplnsh |
|
||||
| replenishment | Rplnsht |
|
||||
| report | Rpt |
|
||||
| represent | Rep |
|
||||
| represented | Repd |
|
||||
| request | Rqst |
|
||||
| required | Reqd |
|
||||
| requirement | Reqt |
|
||||
| requirements | Reqts |
|
||||
| requisition | Req |
|
||||
| reserve | Rsv |
|
||||
| reserved | Rsvd |
|
||||
| reservation | Reserv |
|
||||
| resolution | Resol |
|
||||
| resource | Res |
|
||||
| response | Rsp |
|
||||
| responsibility | Resp |
|
||||
| retain | Rtn |
|
||||
| retained | Rtnd |
|
||||
| return | Ret |
|
||||
| returns | Rets |
|
||||
| revaluation | Revaln |
|
||||
| reverse | Rev |
|
||||
| review | Rvw |
|
||||
| round | Rnd |
|
||||
| rounded | Rndd |
|
||||
| rounding | Rndg |
|
||||
| route | Rte |
|
||||
| routing | Rtng |
|
||||
| routine | Rout |
|
||||
| sales & receivables | Sales |
|
||||
| safety | Saf |
|
||||
| schedule | Sched |
|
||||
| second | Sec |
|
||||
| segment | Seg |
|
||||
| select | Sel |
|
||||
| selection | Selctn |
|
||||
| sequence | Seq |
|
||||
| serial | Ser |
|
||||
| serial number | SN |
|
||||
| service | Serv |
|
||||
| sheet | Sh |
|
||||
| shipment | Shpt |
|
||||
| source | Src |
|
||||
| special | Spcl |
|
||||
| specification | Spec |
|
||||
| specifications | Specs |
|
||||
| standard | Std |
|
||||
| frequency | SF |
|
||||
| statement | Stmt |
|
||||
| statistical | Stat |
|
||||
| statistics | Stats |
|
||||
| stock | Stk |
|
||||
| stockkeeping unit | SKU |
|
||||
| stream | Stm |
|
||||
| structured query language | SQL |
|
||||
| subcontract | Subcontr |
|
||||
| subcontracted | Subcontrd |
|
||||
| subcontracting | Subcontrg |
|
||||
| substitute | Sub |
|
||||
| substitution | Subst |
|
||||
| suggest | Sug |
|
||||
| suggested | Sugd |
|
||||
| suggestion | Sugn |
|
||||
| summary | Sum |
|
||||
| suspended | Suspd |
|
||||
| symptom | Sympt |
|
||||
| synchronize | Synch |
|
||||
| temporary | Temp |
|
||||
| total | Tot |
|
||||
| transaction | Transac |
|
||||
| transfer | Trans |
|
||||
| translation | Transln |
|
||||
| tracking | Trkg |
|
||||
| troubleshoot | Tblsht |
|
||||
| troubleshooting | Tblshtg |
|
||||
| unit of measure | UOM |
|
||||
| unit test | UT |
|
||||
| unrealized | Unreal |
|
||||
| unreserved | Unrsvd |
|
||||
| update | Upd |
|
||||
| valuation | Valn |
|
||||
| value | Val |
|
||||
| value added tax | VAT |
|
||||
| variance | Var |
|
||||
| vendor | Vend |
|
||||
| warehouse | Whse |
|
||||
| web shop | WS |
|
||||
| worksheet | Wksh |
|
||||
| g/l | GL |
|
||||
| % | Pct |
|
||||
| 3-tier | Three-Tier |
|
||||
| Outlook Synch | Osynch |
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=abbreviations+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
39
content/docs/BestPractices/unnecessary-else/index.md
Normal file
39
content/docs/BestPractices/unnecessary-else/index.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: "Unnecessary else"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
`else` should not be used when the last action in the `then` part is an `exit`, `break`, `skip`, `quit`, `error`.
|
||||
|
||||
## Bad code
|
||||
|
||||
```al
|
||||
procedure SomeProcedure()
|
||||
begin
|
||||
if IsAdjmtBinCodeChanged() then
|
||||
Error(AdjmtBinCodeChangeNotAllowedErr, ...)
|
||||
else
|
||||
Error(BinCodeChangeNotAllowedErr, ...);
|
||||
end;
|
||||
```
|
||||
|
||||
## Good code
|
||||
```al
|
||||
procedure SomeProcedure()
|
||||
begin
|
||||
if IsAdjmtBinCodeChanged() then
|
||||
Error(AdjmtBinCodeChangeNotAllowedErr, ...)
|
||||
Error(BinCodeChangeNotAllowedErr, ...);
|
||||
end;
|
||||
```
|
||||
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=unnecessary+else+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
41
content/docs/BestPractices/unnecessary-truefalse/index.md
Normal file
41
content/docs/BestPractices/unnecessary-truefalse/index.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: "Unnecessary true/false"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
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
|
||||
|
||||
```al
|
||||
if Complete <> true then
|
||||
```
|
||||
|
||||
## Good code
|
||||
|
||||
```al
|
||||
if not Complete then
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=unnecessary+true+false+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
48
content/docs/BestPractices/variable-naming/index.md
Normal file
48
content/docs/BestPractices/variable-naming/index.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
title: "Variable Naming"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
Variables that refer to a 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: Record "Job WIP Buffer"
|
||||
```
|
||||
## Good code
|
||||
```al
|
||||
JobWIPBuffer: Record "Job WIP Buffer"
|
||||
```
|
||||
## Bad code
|
||||
```al
|
||||
Postline: Codeunit "Gen. Jnl.-Post Line";
|
||||
```
|
||||
## Good code
|
||||
```al
|
||||
GenJnlPostLine: Codeunit "Gen. Jnl.-Post Line";
|
||||
```
|
||||
## Bad code
|
||||
```al
|
||||
"Amount (LCY)": Decimal;
|
||||
```
|
||||
## Good code
|
||||
```al
|
||||
AmountLCY: Decimal;
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=one+variable+naming+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: "Variables Declarations Order"
|
||||
tags: ["Readability"]
|
||||
categories: ["Best Practice"]
|
||||
---
|
||||
|
||||
_Created by Microsoft, Described by waldo_
|
||||
|
||||
## Description
|
||||
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:
|
||||
|
||||
- Record
|
||||
- Report
|
||||
- Codeunit
|
||||
- XmlPort
|
||||
- Page
|
||||
- Query
|
||||
- Notification
|
||||
- BigText
|
||||
- DateFormula
|
||||
- RecordId
|
||||
- RecordRef
|
||||
- FieldRef
|
||||
- FilterPageBuilder
|
||||
|
||||
(Ref: [Microsoft Docs](https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/analyzers/codecop-aa0021))
|
||||
|
||||
|
||||
## Bad code
|
||||
|
||||
```al
|
||||
StartingDateFilter: Text;
|
||||
Vendor: Record Vendor;
|
||||
```
|
||||
|
||||
## Good code
|
||||
|
||||
```al
|
||||
Vendor: Record Vendor;
|
||||
StartingDateFilter: Text;
|
||||
```
|
||||
|
||||
## [Discussions](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices?discussions_q=one+variables+declarations+order+category%3A%22BC+Best+Practices%22)
|
||||
|
||||
You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices).
|
||||
|
||||
If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article.
|
||||
18
content/docs/NAVPatterns/2-anti-patterns/_index.md
Normal file
18
content/docs/NAVPatterns/2-anti-patterns/_index.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
+++
|
||||
chapter = true
|
||||
title = "2. Anti-Patterns"
|
||||
weight = 130
|
||||
+++
|
||||
Some of the software development practices, had **not** stood the test of time. Despite that, some are still being used today by developers everywhere.
|
||||
|
||||
"An **anti-pattern** (or **antipattern**) is a common response to a recurring problem that is usually ineffective and risks being highly counterproductive" (from [Wikipedia][anchor0])
|
||||
|
||||
Since almost the beginning of the NAV Design Patterns project, we talked about documenting the anti-patterns - but never found the time. Until, spontaneously, the April 1st 2015 article had practically wrote itself in a couple of hours, with priceless contributions coming from Andreas, Elly, Nikola - and last but not least, waldo.
|
||||
|
||||
Best regards,
|
||||
|
||||
Bogdana Botez
|
||||
|
||||
|
||||
|
||||
[anchor0]: http://en.wikipedia.org/wiki/Anti-pattern
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
+++
|
||||
title = "Nav Upgrade"
|
||||
weight = 840
|
||||
+++
|
||||
## Anti-Patterns in NAV Upgrade
|
||||
|
||||
_By Carlos Raul Garcia and Bogdana Botez at Microsoft Development Center Copenhagen_
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
**Context**: when NAV is upgraded, whether on-premises or in the cloud, developers have the chance to write upgrade code to move data across changing data structures. Writing good quality code will help successful upgrades.
|
||||
|
||||
### General on upgrade
|
||||
|
||||
**Problem:** assuming that the upgrade table contains data.
|
||||
|
||||
If the table is empty, it means that either the upgrade has run, or there was no data in the original tenant; in both cases, the upgrade code should exit immediately.
|
||||
|
||||
**Solution:** if using an upgrade table, always validate that the table contains data before doing anything.
|
||||
|
||||
### Upgrade code, can it be rerun safely?
|
||||
|
||||
**Problem**: if the upgrade code is not written in a way that makes it runnable twice ([idempotent][anchor1]), then several failures can happen, including something as critical as data corruption.
|
||||
|
||||
In on premise NAV installations, if something fails at upgrade, there is no way to run only the "remaining" tasks. You will need to run the whole upgrade again, and might end-up with data that you cannot trust.
|
||||
|
||||
What about the cloud? In Platform As A Service (PaaS), in some situations, the upgrade code needs to be run twice (for example, when moving tenants from a broken/frozen VM to a healthy one).
|
||||
|
||||
**Solution:** Make sure each of your upgrade procedures only kicks in if it didn't run before.
|
||||
|
||||
**Examples**
|
||||
|
||||
The examples below have happened in real live NAV PaaS upgrade:
|
||||
|
||||
**Table data overwrite**
|
||||
|
||||
* **Problem**: at upgrade, a new column has been added to a table and initialized with default values. In the meanwhile, during production, some of the default values are changed to production real life values. The second time the upgrade runs, those values will be overwritten with defaults, any personalization lost.
|
||||
* **Solution:** before initializing with default values, check if non-default values exist.
|
||||
|
||||
**Crash on math operations**
|
||||
|
||||
* **Problem:** one tenant upgrade managed to divide by zero, by assuming a non-zero value.
|
||||
* **Solution:** don't assume values can never be zero, always check before using them in divisions.
|
||||
|
||||
**Use of external components**
|
||||
|
||||
* **Problem:** a one-time registration through web services to an external service failed when attempting to register a second time.
|
||||
* **Solution:** check if already registered, before attempting again.
|
||||
|
||||
### Parallelism
|
||||
|
||||
**Problem**: Upgrade procedures can be run in parallel, causing issues when different procedures attempt to modify the same table at the same time.
|
||||
|
||||
When modifications to the same table are being made from two or more different procedures, there is no guarantee on sequential run, or on a certain order they will be run in.
|
||||
|
||||
**Solution**: if sequential or ordered execution is needed, make the affected upgrade procedures local and call them all, in the desired sequence, from a public upgrade procedure.
|
||||
|
||||
### Access to cloud machines
|
||||
|
||||
**Problem:** dependencies on manual installation steps do not fit in the cloud.
|
||||
|
||||
If Dynamics NAV is installed on-premises, then any additional setup (like dependencies of external dlls, manual configuration steps etc.) can be done manually or semi-manually by the IT admin, at first setup and upgrade.
|
||||
|
||||
In the cloud, NAV partners don't have access to the machines -- hence they cannot deploy and configure those external dependencies as they did in the old on-premises installations.
|
||||
|
||||
**Solution:** Don't assume you will have access to PaaS or SaaS machines. Build your solution in such a way that it doesn't depend on executing manual configurations on the host machine.
|
||||
|
||||
|
||||
|
||||
[anchor0]: upgrade.png
|
||||
[anchor1]: http://stackoverflow.com/questions/1077412/what-is-an-idempotent-operation#1077421
|
||||
|
||||
|
||||
[image0]: upgrade.png
|
||||
BIN
content/docs/NAVPatterns/2-anti-patterns/nav-upgrade/upgrade.png
Normal file
BIN
content/docs/NAVPatterns/2-anti-patterns/nav-upgrade/upgrade.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2 KiB |
|
|
@ -0,0 +1,94 @@
|
|||
+++
|
||||
title = "Reusable Bugs"
|
||||
weight = 1020
|
||||
+++
|
||||
_By Bogdana Botez, Andreas Moth, Eric Wauters (waldo), Elly Nkya, Nikola Kukrika_
|
||||
|
||||
_April 1st, 2015_
|
||||
|
||||
## Abstract
|
||||
|
||||
It is difficult to come up with new and different bugs in each release, and it is a waste of resources to not use the ones which already had proven successful in the past. To avoid reinventing the wheel, we propose to come up with new innovative ways to create bugs that are reusable and generic enough to be used in various places of the application.
|
||||
|
||||
## Examples
|
||||
|
||||
**Option Strings**
|
||||
|
||||
One example of a reusable bug is to find a heavily used table, like table 36 Sales Header, and observe one of the more important fields of type Option, for example Document Type. The OptionString property has the following value: Quote,Order,Invoice,Credit Memo,Blanket Order,Return Order, which you should update to add one option, for example Transport Order in the beginning.
|
||||
|
||||
The main benefit of this reusable bug is that code changed in one place, has impact in multiple sides on the application: document creation and handling, posting etc.
|
||||
|
||||
**Application Management**
|
||||
|
||||
Codeunit 1, ApplicationManagement is a good place for creating reusable bugs. A simple INSERT on the RecRef on the first line of OnDatabaseInsert will create a sure and versatile bug that is reusable all over the application. Redundancy on database insertion ensures that data is surely conveyed to the data storage layer, twice.
|
||||
|
||||
**God objects**
|
||||
|
||||
We are considering to no longer make localization builds for countries. It consumes lab resources to keep running multiple country builds. Instead, we will merge everything into one single build. This is a bigger scale implementation of the ["God object"][anchor0]. Inside this unique build, we will use IF statements and sporadically CASE statements to select each country's behavior. For more help on how to use IFs, see also the IF .. THEN|ELSE C/AL Coding Guideline.
|
||||
|
||||
**Field length economy**
|
||||
|
||||
When you post a document, make sure to transfer data to a field that is smaller than the field you are assigning from. This will not immediately be caught and will only hurt a subset of the customers who uses Microsoft Dynamics NAV to its fullest.
|
||||
|
||||
## Guidelines
|
||||
|
||||
When bug addition is not entirely obvious, there is a second way to approach the problem. By following some general coding best practices like the ones described below, reliable bug innovation is facilitated and can come naturally as a collateral effect.
|
||||
|
||||
**Code structure**
|
||||
|
||||
Put everything in one function and only use comments to explain the structure of your code. And don't use functions - because this only complicates things... having to navigate from function to function, and completely lose track of where you are in the business logic.
|
||||
|
||||
On top of that .. put everything in one codeunit. Because also that will simplify and make your structure more readable.
|
||||
|
||||
Use Hungarian Notation on your variables, because at any time, you need to know what type, and what context your variable is on.
|
||||
|
||||
Declare all your functions and variables global, so they are available at any time.
|
||||
|
||||
**Don't do Unit Testing**
|
||||
|
||||
Unit testing adds complexity and extra time to the stuff you're doing. Also, it eats up extra codeunits which means: it costs money. You will never be able to foresee all scenarios possible, so you're destined to forget and not test everything. So you will save time in not doing unit testing.
|
||||
|
||||
**Never add images to actions**
|
||||
|
||||
Because there is an image by default. When you don't provide an image on an action on a page, the application will foresee a ball... and when you never do it, your application will have a very consistent way of showing your actions, by providing that picture of that ball. On top of that, you'll save time.
|
||||
|
||||
**Do not care about ControlIDs**
|
||||
|
||||
When you're doing development of your product, do not care about ControlIDs, and just leave the Offset ID to the default value of 0\. This way, when merging, you will receive nice notifications, saying both you and Microsoft have added functions in that objects. You can use this feature to document all these places.
|
||||
|
||||
**Hooks**
|
||||
|
||||
Never apply the hook pattern. Hooks will only reduce upgrade time. This means, you will only shortly enjoy using the AMU (Application Merge Utilities). The more you change in default application, the longer it takes to upgrade, the longer you will enjoy the toolkit
|
||||
|
||||
This can be taken one level higher. Simply you are not hardcore if you do not use notepad to resolve all of the merge issues.
|
||||
|
||||
**How to use RecRef**
|
||||
|
||||
Why fuss around declaring specific table variables, just generalize, all you need is one, two, or perhaps three RecRef variables, with a few IFs and CASEs here and there for reflection, to carry you all the way.
|
||||
|
||||
**Arguments**
|
||||
|
||||
Using only a few arguments on the functions is a sign of a weak developer. Stick in as many arguments as possible on the function, even if you are not using them, they could be useful in the future.
|
||||
|
||||
**Just another field / action**
|
||||
|
||||
Thinking of the design is overrated, each problem can be solved by adding an additional field or the table/page or with adding another action. We all know this has worked well in the past.
|
||||
|
||||
**Reusability**
|
||||
|
||||
We have decided that each time we fix a bug, we now also explain how it can be applied as a pattern. We then use anti-virus software to search for these patterns, to make sure we do not re-introduce these bugs anywhere else in NAV.
|
||||
|
||||
**Business logic placement**
|
||||
|
||||
As a best practice, we have also decided to move code into pages. Business logic should no longer be in tables and codeunits, but instead pages should know and be aware of the context and update it accordingly. As opposite to tables and codeunits, pages are aware of the context.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Happy April Fools' Day.
|
||||
|
||||
Disclaimer: this is inspired from IETF documentation published on April 1st, like for example the revolutionizing [IP over Avian Carriers][anchor1] standard.
|
||||
|
||||
|
||||
|
||||
[anchor0]: http://en.wikipedia.org/wiki/God_object
|
||||
[anchor1]: http://en.wikipedia.org/wiki/IP_over_Avian_Carriers
|
||||
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,...);
|
||||
```
|
||||
42
content/docs/NAVPatterns/4-get-involved/_index.md
Normal file
42
content/docs/NAVPatterns/4-get-involved/_index.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
+++
|
||||
chapter = true
|
||||
title = "(OLD) Get Involved"
|
||||
weight = 170
|
||||
+++
|
||||
|
||||
**Reminder, this is an ARCHIVE of the Patterns site, this information is not current.**
|
||||
---
|
||||
|
||||
**Spread the info**
|
||||
|
||||
NAV Design Patterns are excellent materials for training and knowledge transfer. In your company, you can help new developer get to speed with NAV by recommending them to read and then present to the team one of the most common patterns: [No. Series][anchor0], [Setup Table][anchor1] and definitely [Hooks ][anchor2]which will be a great investment in reducing your upgrade time. The more experienced developers can read directly the newer patterns, like [Surrogate Key][anchor3], [Easy Update][anchor4], [Totals on Subpages][anchor5], [Using Queries instead of nested loops][anchor6] etc.
|
||||
|
||||
**Become a NAV Design Pattern author**
|
||||
|
||||
You have a pattern that you have used successfully? You have ideas on new patterns? You've found some existing design patterns which are used in the product but nobody (except a few) knows how it works, but you find it worth it to explain it for the NAV C/AL developers out there?
|
||||
|
||||
Send your pattern idea to [Bogdana Botez][anchor7] as a private message on the community webpage. Once your first pattern is ready, we will review it as a team, and when signed off, you get author permissions on the Wiki site and from then on, you can continue publishing and editing existing patterns. However, only publish on the Wiki materials that we had signed-off (we don't have moderation capabilities yet, so we count on each author to only make meaningful/agreed changes).
|
||||
|
||||
You and your company also get credit by being mentioned on the pattern and also on our patterns authors page.
|
||||
|
||||
Once you have the idea, writing it down shouldn't take long. You will be helped by adopting [the template ][anchor8]that we've used.
|
||||
|
||||
**Remember the rules**
|
||||
|
||||
When handling design patterns, content quality is critical. We are trying our best to only publish content that is correct, relevant and has been reviewed by multiple developers. Therefore, we review and sign-off all patterns before publishing them (except for the videos made prior to 2015). All text content found on this Wiki and on the NAV Team Blog has been through one, usually multiple iterations of review. If you find something to correct, please comment on the pattern or contact [Bogdana Botez][anchor9], and we will review and update it.
|
||||
|
||||
We are working on creating a set of rules, which would help keeping the content clean and the project on the correct track. [Find the rules here][anchor10].
|
||||
|
||||
|
||||
|
||||
[anchor0]: /navpatterns/1-patterns/no-series/ "No. Series"
|
||||
[anchor1]: /navpatterns/1-patterns/singleton/singleton-table/setup-table/ "Setup Table"
|
||||
[anchor2]: /navpatterns/1-patterns/hooks/ "Hooks"
|
||||
[anchor3]: /navpatterns/1-patterns/implementation-of-surrogate-keys-using-autoincrement-pattern/
|
||||
[anchor4]: /navpatterns/1-patterns/easy-update-of-setup-or-supplementary-information/
|
||||
[anchor5]: /navpatterns/1-patterns/totals-and-discounts-on-subpages-sales-and-purchases/
|
||||
[anchor6]: /navpatterns/1-patterns/queries/use-queries-to-replace-nested-loops/
|
||||
[anchor7]: /members/bogdana-botez/default.aspx
|
||||
[anchor8]: /navpatterns/4-get-involved/template-for-writing-nav-design-patterns/
|
||||
[anchor9]: /members/bogdana-botez/default.aspx "Bogdana Botez"
|
||||
[anchor10]: /navpatterns/4-get-involved/code-of-conduct/ "Find the rules here"
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
+++
|
||||
title = "Code of Conduct"
|
||||
weight = 330
|
||||
+++
|
||||
Find below the rules to be used when disseminating or relating to the NAV Design Patterns.
|
||||
|
||||
1. Only use materials published in text on the NAV Design Patterns Wiki site. If you received from us, any unpublished materials, please observe that they are subject to change and have not been approved for external use.
|
||||
2. When referencing a NAV Design Pattern, you must remember to also reference its author and company where the author is employed. You will find the author and his/her company at the beginning of each pattern, under the title.
|
||||
3. When referencing a NAV Design Patterns project, you must make it clear that this is a community project, driven by Microsoft, with multiple developers involved from both Microsoft and the community.
|
||||
4. When using published text content of NAV Design Patterns, do not alter the text in any way that was published on the Wiki site, unless is has been reviewed by the patterns team and signed off by someone at Microsoft in writing.
|
||||
5. If you have other materials which have not received explicit signoff in writing from me, where I have specifically stated that they are valid design patterns ready for publishing, please do not name them "NAV Design Patterns" (or anything similar). You are free to use your own content, but do not associate it in any way with NAV Design Patterns unless it is signed off in writing.
|
||||
6. If you do choose to use your own content, you must make it clear that it is not a NAV Design Pattern.
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
+++
|
||||
title = "Patterns Authors"
|
||||
weight = 930
|
||||
+++
|
||||
This is the list of people that have been part of the NAV Design Patterns team. If you would like to join the project follow the instructions provided on [Be a NAV Pattern Author][anchor0] page.
|
||||
|
||||
Project administrator: [Bogdana Botez][anchor1].
|
||||
|
||||
Authors (in alphabetical order):
|
||||
|
||||
* Abshishek Ghosh, Microsoft (4 patterns)
|
||||
* Using Query Objects to Detect Duplicates
|
||||
* Blocked Entity
|
||||
* Single-Record (Setup) Table
|
||||
* Temporary Dataset Report
|
||||
|
||||
* Anders Larsen, Microsoft (1 pattern)
|
||||
|
||||
* Easy Update of Setup or Supplementary Information
|
||||
|
||||
* Andreas Moth, Microsoft (1 pattern)
|
||||
|
||||
* Anti-pattern: Reusable bugs
|
||||
|
||||
* Bogdan Sturzoiu, Microsoft (4 patterns)
|
||||
|
||||
* Feature Localization for Data Structures
|
||||
* Copy Document
|
||||
* Using C/AL Query Objects Instead of Nested Loops
|
||||
* Data-Driven Blocked Entity
|
||||
|
||||
* Bogdana Botez, Microsoft (18 patterns)
|
||||
|
||||
* Silent File Upload and Download
|
||||
* Standard Journal
|
||||
* No. Series
|
||||
* Data Model Proxy
|
||||
* Journal Error Processing
|
||||
* Journal Template-Batch-Line
|
||||
* Multilanguage Application Data
|
||||
* SELECT DISTINCT using Queries
|
||||
* Anti-patterns: reusable bugs
|
||||
* Sensitive Data Encapsulation
|
||||
* Data Encryption
|
||||
* Single Point of Access
|
||||
* Masked Text
|
||||
* SSL in NAV
|
||||
* Singleton
|
||||
* Singleton Codeunit
|
||||
* Anti-Patterns in NAV Upgrade
|
||||
* Cue table
|
||||
|
||||
* Carlos Raul Garcia, Microsoft (1 pattern)
|
||||
|
||||
* Anti-Patterns in NAV Upgrade
|
||||
|
||||
* Ciprian Iordache, Microsoft (2 patterns)
|
||||
|
||||
* Activity Log
|
||||
* Totals and Discounts on Subpages (Sales and Purchases)
|
||||
|
||||
* David Bastide, Microsoft (3 patterns)
|
||||
* Notification Lifecycle Management pattern
|
||||
* Data Migration Façade
|
||||
* Extending the Role Center Headlines
|
||||
|
||||
* Elly Nkya, Microsoft (2 patterns)
|
||||
|
||||
* Singleton Table
|
||||
* Anti-patterns: reusable bugs
|
||||
|
||||
* Eric Wauters (waldo), iFacto, PRS (6 patterns)
|
||||
|
||||
* Hooks
|
||||
* Posting Routine - Select Behaviour
|
||||
* Variant Facade
|
||||
* Argument Table
|
||||
* Discovery Event
|
||||
* Anti-patterns: reusable bugs
|
||||
|
||||
* Gary Winter, agiles (1 pattern)
|
||||
* Variant Façade
|
||||
|
||||
* Henrik Langbak, Kim Ginnerup, Bording Data A/S (2 patterns)
|
||||
|
||||
* Currently Active Record
|
||||
* Released Entity
|
||||
|
||||
* Jan Hoek, IDYN (2 patterns)
|
||||
|
||||
* Conditional Cascading Update
|
||||
* Setup Specificity Fallback
|
||||
|
||||
* Jesper Schulz, Microsoft (1 pattern)
|
||||
|
||||
* Error Message Processing, part I
|
||||
|
||||
* Martin Dam, Microsoft (1 pattern)
|
||||
|
||||
* Multi-File Download
|
||||
|
||||
* Mike Borg Cardona, Microsoft (1 pattern)
|
||||
|
||||
* Creating URLs to NAV Clients
|
||||
|
||||
* Mostafa Balat, Microsoft (3 patterns)
|
||||
|
||||
* .NET Exception Handling
|
||||
* Cached Web Service Calls
|
||||
* Try Method
|
||||
|
||||
* Nikola Kukrika, Microsoft (7 patterns)
|
||||
|
||||
* Totals and Discounts on Subpages (Sales and Purchases)
|
||||
* Create Data from Templates
|
||||
* Argument Table
|
||||
* Instructions in UI
|
||||
* Creating Custom Charts
|
||||
* Variant Façade
|
||||
* Anti-patterns: reusable bugs
|
||||
|
||||
* Nikolai L'Estrange, from TVision Technology Ltd. in the UK_
|
||||
_ (2 patterns)
|
||||
|
||||
* Observer
|
||||
* Cross Session Events
|
||||
|
||||
* Raed Selim, Microsoft (1 pattern)
|
||||
* Product Name
|
||||
|
||||
* Soumya Dutta (2 patterns)
|
||||
* In-context notifications
|
||||
* Data Migration Façade
|
||||
|
||||
* Søren Klemmensen, 360 Visibility, PRS (2 patterns)
|
||||
|
||||
* Master Data
|
||||
* Surrogate Key
|
||||
|
||||
* Tim Grant, Trendmicro (1 pattern)
|
||||
|
||||
* Read Once Setup Record
|
||||
* Report Selector (this pattern was started by 2 workgroups by multiple authors, but finalized and corrected by Tim).
|
||||
|
||||
* Xavier Garonnat, knk Ingénierie (1 pattern)
|
||||
|
||||
* Document
|
||||
|
||||
They have also invested their time and energy in this project:
|
||||
|
||||
* Eva Dupont, Microsoft - our publisher on MSDN NAV Team Blog.
|
||||
* Kurt Juvyns, Microsoft - coordinator of pattern videos
|
||||
|
||||
Pattern evangelists:
|
||||
|
||||
* Eric Wauters (waldo), iFacto, PRS
|
||||
* Gary Winter, agiles, PRS
|
||||
* Mark Brummel, Brummel Dynamics Services, PRS
|
||||
* Mike Doster, Mergetool
|
||||
* Søren Klemmensen, 360 Visibility, PRS
|
||||
|
||||
And last but not least, we have collaborated with Plataan who hired Eric Wauters from ifacto and Mark Brummel from Brummel Dynamics Services and PRS, to publish on video some of our patterns.
|
||||
|
||||
|
||||
|
||||
[anchor0]: /navpatterns/4-get-involved/
|
||||
[anchor1]: /members/bogdana-botez/default.aspx "NAV Design Patterns project administrator"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
|
|
@ -0,0 +1,59 @@
|
|||
+++
|
||||
title = "Template for writing Nav Design Patterns"
|
||||
weight = 1180
|
||||
+++
|
||||
This is a guideline, some parts are optional (if there's no content, remove the whole paragraph).
|
||||
|
||||
<_Your name here in italics, plus your company name_\>
|
||||
|
||||
## **<<Pattern Title\>\>**
|
||||
|
||||
Short, descriptive and easy to remember.
|
||||
|
||||
## **Pattern Logo**
|
||||
|
||||
Black & white, no text on it.
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
**Context**: Sets the stage where the pattern takes place. 1-2 sentences.
|
||||
|
||||
**Problem**: What happens before this pattern is used? How can it go wrong? 1-5 lines.
|
||||
|
||||
**Forces:** (explain why the problem is difficult to solve; state the considerations that must be taken into account when choosing a solution to a problem)
|
||||
|
||||
* <<**Force 1: **short description (What is the impact of not using this pattern? Or using only partially?) \>\>
|
||||
* <<**Force 2: **short description \>\>
|
||||
* ...
|
||||
|
||||
**Solution:** 1-2 sentences. The full description will come below.
|
||||
|
||||
<<Solution descriptions\>\>
|
||||
|
||||
<<Diagrams. Please add numbers and captions to both figures and tables\>\>
|
||||
|
||||
**Usage**: <<short usage description\>\>
|
||||
|
||||
<<Examples\>\>
|
||||
|
||||
<<Where it's used in Dynamics NAV\>\>
|
||||
|
||||
**Benefits:**
|
||||
|
||||
* **<< Benefit 1: **solves Force 1, short description\>\>
|
||||
* **<< Benefit 2: **solves Force 2, short description\>\>
|
||||
* ...
|
||||
|
||||
**Consequences:**
|
||||
|
||||
* **<<Consequence 1: **are there any drawbacks? Is there anything the developer needs to be aware of when implementing this pattern? Any known limitations? **\>\>**
|
||||
* **...**
|
||||
|
||||
**List of references**
|
||||
|
||||
|
||||
|
||||
[anchor0]: PatternLogo.png
|
||||
|
||||
|
||||
[image0]: PatternLogo.png
|
||||
16
content/docs/NAVPatterns/_index.md
Normal file
16
content/docs/NAVPatterns/_index.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
+++
|
||||
title = "NAV Patterns Archive"
|
||||
weight = 20
|
||||
+++
|
||||
|
||||
## About the archive
|
||||
|
||||
This section of the site is a careful reproduction of the content of the Original Microsoft Community NAV Design Patterns project, created with permission.
|
||||
|
||||
## Reading the archive
|
||||
|
||||
Bear in mind, many of the style and formatting guidelines in this section have been brought forward into:
|
||||
- The automatic formatting provided by the AL Extension
|
||||
- The Code Analyzers
|
||||
|
||||
Additionally, a variety of topics around the Windows Client and DotNet are outdated, and should only be used for either reference or if working in older environments.
|
||||
12
content/docs/NAVPatterns/patterns/_index.md
Normal file
12
content/docs/NAVPatterns/patterns/_index.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: "Patterns"
|
||||
weight: 110
|
||||
tags: ["NAV", "C/AL"]
|
||||
categories: ["Archived Pattern"]
|
||||
description: >
|
||||
Patterns described to be used with Microsoft Dynamics NAV
|
||||
---
|
||||
|
||||
{{% alert title="Warning" color="warning" %}}
|
||||
Please note that these patterns may not be up-to-date with the patterns for AL and Business Central Development.
|
||||
{{% /alert %}}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
BIN
content/docs/NAVPatterns/patterns/activity-log/Activity-Log.jpg
Normal file
BIN
content/docs/NAVPatterns/patterns/activity-log/Activity-Log.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
116
content/docs/NAVPatterns/patterns/activity-log/index.md
Normal file
116
content/docs/NAVPatterns/patterns/activity-log/index.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
+++
|
||||
title = "Activity Logs"
|
||||
weight = 210
|
||||
+++
|
||||
_Originally by Ciprian Iordache at Microsoft Development Center Copenhagen_
|
||||
|
||||
## Activity Log
|
||||
|
||||
**Abstract**
|
||||
|
||||
The Activity Log pattern tracks execution of activities. This is a Dynamics NAV specific implementation of the [Audit Log][anchor0] pattern.
|
||||
|
||||
[![ ][image0]][anchor1]
|
||||
|
||||
**Problem**
|
||||
|
||||
In general, integrating with external systems can be very challenging, due to the complexity of the situation -- connectivity issues, asynchronous operations, user errors, etc. These challenges require sometimes re-trying several times, polling the external system, re-send/re-get data as all these activities can succeed but can very well fail.
|
||||
|
||||
Similar challenges exist in situations where a lengthy, complex task, composed of different steps is to be executed by various people in various timeframes. In case of errors (but sometimes also in case of success) there will be a need to track these activities to see what happened and the actual person which did a specific step.
|
||||
|
||||
In all these cases, we need to be able to troubleshoot.
|
||||
|
||||
A tracking/logging functionality could be implemented for each activity/step separately, but this would lead to code duplication and problems in maintaining the code in future.
|
||||
|
||||
In NAV there is already the Change Log functionality which can record all the data changes that have been done to specific tables, specific fields. However, this functionality is not available for activities. Also, there are few places where separate logging/tracking implementations were done but the current pattern proposes an unified, central way of data recording and enables the user to track all/most of the activities.
|
||||
|
||||
**Solution**
|
||||
|
||||
The Activity Log pattern tracks specific outcome of the activities, in order to be able to assess what went wrong/fine or who performed a specific activity.
|
||||
|
||||
Activity Log pattern
|
||||
|
||||
* records the activity and its outcome (error or success messages)
|
||||
* assembles all messages in one central view and presents them to the user filtered for the specific activity and ordered in reverse chronological order.
|
||||
|
||||
Figure below illustrates the how the Activity Log manifests in the UI. The figure shows a part of an activity log for a posted document that was sent to the document exchange service and illustrates both successful and failed activities.
|
||||
|
||||
[![ ][image1]][anchor2]
|
||||
|
||||
This functionality is implemented in the following way - Activity Log table (TAB710) contains a simple function that allows you to log the result of a task or activity:
|
||||
|
||||
ActivityLog.LogActivity(ContextRecordID,ActivityLog.Status::Failed,ContextDescription,ActivityDescription,ActivityMessage);
|
||||
|
||||
Similar to TAB700 for Error Messaging, the Activity Log table contains a RECORDID that is a link to the parent/context entity. That permits the Activity Log to be used in a generic way, for any kind of entities (tables) and it also permits filtering the data to a specific related entity only before being presenting to the user.
|
||||
|
||||
The following parameters should be provided to the function:
|
||||
|
||||
* RecordID: The record/context for which the activity is logged
|
||||
* Status: The task/activity outcome
|
||||
* Descriptions/Messages: fields that will clearly describe the state and outcome of the task
|
||||
|
||||
To show the log, add a page action, with the caption including the name "<prefix\> Log" and link it to the image named "Log":
|
||||
|
||||
```AL
|
||||
{ ;1 ;Action ;
|
||||
Name=ActivityLog;
|
||||
CaptionML=ENU='Activity Log';
|
||||
ToolTipML=ENU='View the status and any errors if the document was sent as an electronic document or OCR file through the document exchange service.';
|
||||
ApplicationArea=#Basic,#Suite;
|
||||
Image=Log;
|
||||
OnAction=
|
||||
VAR
|
||||
ActivityLog@1000 : Record 710;
|
||||
BEGIN
|
||||
ActivityLog.ShowEntries(RECORDID);
|
||||
END;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**NAV usages**
|
||||
|
||||
In Dynamics NAV 2016, there is a new feature for sending documents in electronic format to a document exchange service. In this case, sending documents requires multiple steps as it is an asynchronous activity and as such, in order to keep track of what's happening and when the Activity Log functionality was used. That offers later the possibility to see who sent and when a document was sent, when it was dispatched, if any dispatch errors and how many tries have been made until the document was finally dispatched or rejected.
|
||||
|
||||
So as usages in NAV 2016, we have the document exchange and OCR features plus the related posted documents involved in the document exchange feature.
|
||||
|
||||
* COD1294.TXT
|
||||
* COD1410.TXT
|
||||
* PAG1270.TXT
|
||||
* PAG1275.TXT
|
||||
* PAG143.TXT
|
||||
* PAG144.TXT
|
||||
* PAG189.TXT
|
||||
* TAB112.TXT
|
||||
* TAB114.TXT
|
||||
* TAB130.TXT
|
||||
|
||||
**Ideas for improvement**
|
||||
|
||||
Replace the scattered similar functionality (as mentioned above, we have several places having close functionality or similar requirements) with this new pattern.
|
||||
|
||||
**Consequences**
|
||||
|
||||
* Use with caution, similar to the Change Log functionality, as if the pattern will be used extensively in all the activities/operations within NAV, the table might become large containing many records and might cause some performance issues when presenting the data to the client (filtering on the specific activity).
|
||||
* Do not log private or confidential information (passwords, amounts, salaries, sensitive data), unless you are ok with this data to be showed to all users (even to users which normally would not have access to this data), thus overriding the permission sets.
|
||||
* Log only essential information (quality over quantity). Can the logged data be used to analyze the problem, or is it just junk data?
|
||||
|
||||
**NAV Versions**
|
||||
|
||||
Supported from NAV 2016
|
||||
|
||||
**Related Topics**
|
||||
|
||||
Error Message Processing -- provides a similar view and uses similar concepts: has a generic implementation (uses as link the same RECORDID feature) and uses same filtering functionality when displaying the data to the user.
|
||||
|
||||
Audit Log -- as mentioned in the beginning, this pattern is a NAV specific implementation of the audit log pattern.
|
||||
|
||||
|
||||
|
||||
[anchor0]: http://martinfowler.com/eaaDev/AuditLog.html
|
||||
[anchor1]: Activity-Log.jpg
|
||||
[anchor2]: Activity-Log-NAV.jpg
|
||||
|
||||
|
||||
[image0]: Activity-Log.jpg
|
||||
[image1]: Activity-Log-NAV.jpg
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
123
content/docs/NAVPatterns/patterns/argument-table/index.md
Normal file
123
content/docs/NAVPatterns/patterns/argument-table/index.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
+++
|
||||
title = "Argument Table"
|
||||
weight = 220
|
||||
+++
|
||||
_Originally By Nikola Kukrika and waldo_
|
||||
|
||||
### Abstract
|
||||
|
||||
The Argument Table pattern is used to provide an extension point for adding new arguments without changing the signature. By grouping multiple arguments into a table the code becomes more readable (function signature and the usage of the function).
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
### Problem
|
||||
|
||||
In CAL overloading function signature is not supported. It is also not possible to provide default values for the function arguments.
|
||||
|
||||
When an argument needs to be added to the function, the existing function needs to be extracted to a new method with an additional argument and the original function will call new method. This will cause an upgrade problem in the future, since the entire body of the method is replaced.
|
||||
|
||||
Second commonly occurring problem is option duplication. In order to pass options often they are duplicated in the signature.
|
||||
|
||||
The last problem that can be solved is high number of arguments. Functions with a high number of arguments are hard to understand. Having arguments grouped within the table with a meaningful name will improve readability and make code easier to understanding.
|
||||
|
||||
Few examples of the bad implementations are as illustrated here:
|
||||
|
||||
#### Bad example 1
|
||||
|
||||
|
||||
```AL
|
||||
PROCEDURE FillInVATReturnData@1200001(VAR DeclarationID@1200000 : Code [20];VAR LineID@1200001 : Code [20];VAR PeerID@1200002 : Code [20]; VAR DocumentNo@1200003: Code[20]; VAR NumberOfCopies@1200007: Integer; VAR Uploaded@1200004 : Boolean; VAR Correction@1200005 : Boolean; VAR HasValidationErr@1200006 : Boolean);
|
||||
```
|
||||
|
||||
**Call**
|
||||
|
||||
|
||||
```AL
|
||||
FillInVATReturnData(NoSeries, NextLineID, CustomerID, DocumentNo, SingleCopy, ???, ??, ...., ...)
|
||||
```
|
||||
|
||||
In this example the code is hard to read and understand. Adding an additional argument will require refactoring of the existing function. Each time a new argument is added a new function will be created.
|
||||
|
||||
#### Bad example 2
|
||||
|
||||
```AL
|
||||
LOCAL PROCEDURE GetTableSyncSetupW1@3(OldTableId@1002 : Integer; VAR UpgradeTableId@1001 : Integer; VAR TableUpgradeMode@1000 : 'Check, Copy, Move, Force') : Boolean;
|
||||
BEGIN
|
||||
CASE OldTableId OF
|
||||
DATABASE::"Sales Header":
|
||||
SetTableSyncSetup(0,TableUpgradeMode::Check,UpgradeTableId,TableUpgradeMode);
|
||||
DATABASE::"Posting Exch. Column Def":
|
||||
SetTableSyncSetup(104025,TableUpgradeMode::Copy,UpgradeTableId,TableUpgradeMode);
|
||||
DATABASE::"Payment Export Data":
|
||||
SetTableSyncSetup(0,TableUpgradeMode::Force,UpgradeTableId,TableUpgradeMode);
|
||||
ELSE
|
||||
EXIT(FALSE);
|
||||
END;
|
||||
EXIT(TRUE);
|
||||
END;
|
||||
```
|
||||
|
||||
In this example each time a new argument is added all function calls will have to be updated. Option is duplicated in the signature, which will cause issues if a new option is defined or the existing options are renamed.
|
||||
|
||||
### Solution
|
||||
|
||||
By grouping the arguments within the table it is possible to add additional argument and reuse it where it is needed without changing the signature.
|
||||
|
||||
Multiple parameters are grouped within the single object with a meaningful name so the code becomes more readable.
|
||||
|
||||
It is possible to assign default values and to have the code validation.
|
||||
|
||||
Argument table should preferably be a temporary table since the implementation is simpler.
|
||||
|
||||
The examples of usages addressing problems shown above are:
|
||||
|
||||
#### Good example 1
|
||||
|
||||
New table
|
||||
```AL
|
||||
TAB 50003 VAT Return Data
|
||||
PROCEDURE FillInVATReturnData@1200001(VAR VATReturnData@1200000 : Record 50003);
|
||||
|
||||
VATReturnData.INIT;
|
||||
VATReturnData.NumberOfCopies := GetDefaultNumberOfCopies;
|
||||
VATReturnData.Uploaded := FALSE;
|
||||
|
||||
FillInVATReturnData(VATReturnData);
|
||||
```
|
||||
|
||||
By introducing an argument table, code is much more readable since there is a single argument for a function. It is easy to see which arguments are passed in and which are modified in a function.
|
||||
|
||||
#### Good example 2
|
||||
|
||||
Good example
|
||||
```AL
|
||||
PROCEDURE GetTableSyncSetupW1@3(VAR TableSynchSetup@1000 : Record 2000000135);
|
||||
BEGIN
|
||||
SetTableSyncSetup(DATABASE::"Sales Header",0,TableSynchSetup.Mode::Check);
|
||||
SetTableSyncSetup(DATABASE::"Posting Exch. Column Def",104025,TableSynchSetup.Mode::Copy);
|
||||
SetTableSyncSetup(DATABASE::"Payment Export Data",0,TableSynchSetup.Mode::Force);
|
||||
END;
|
||||
```
|
||||
|
||||
Option definition is not encapsulated within the table. Arguments are grouped and we can add additional arguments without the need to change the signature.
|
||||
|
||||
### Downsides
|
||||
|
||||
You need to create one more table
|
||||
|
||||
Complex types can't be embedded as fields in tables (cannot have a record field type etc).
|
||||
|
||||
### NAV Usages
|
||||
|
||||
Upgrade Codeunits
|
||||
|
||||
### Related Patterns
|
||||
|
||||
Posting Routine, Select behavior: Setting fields on existing records in order not to change the signatures.
|
||||
|
||||
|
||||
|
||||
[anchor0]: 0218.Argument-Table-image.png
|
||||
|
||||
|
||||
[image0]: 0218.Argument-Table-image.png
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue