This commit is contained in:
JeremyVyska 2022-02-20 20:13:18 +00:00
parent 34af3362da
commit 2d866c6316
732 changed files with 136202 additions and 67025 deletions

View file

@ -0,0 +1,316 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>alguidelines.dev - Business Central Design Patterns Design</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/</link><description>Recent content in Design on alguidelines.dev - Business Central Design Patterns</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><atom:link href="https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/index.xml" rel="self" type="application/rss+xml"/><item><title>Docs: By Reference Parameters</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/by-reference-parameters/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/by-reference-parameters/</guid><description>
&lt;p>Do not declare parameters by reference if their values are not intended to be changed.&lt;/p>
&lt;p>Unintentional value changes might propagate. Also, it might lead people to believe that value changes are intended.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>LOCAL PROCEDURE ShowMessage@15(VAR Text@1000 : Text[250]);
BEGIN
Text := GetMessageText;
IF (Text &amp;lt;&amp;gt; '') AND GenJnlLineInserted THEN
MESSAGE(Text);
END;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>LOCAL PROCEDURE ShowMessage@15(Text@1000 : Text[250]);
BEGIN
Text := GetMessageText;
IF (Text &amp;lt;&amp;gt; '') AND GenJnlLineInserted THEN
MESSAGE(Text);
END;
&lt;/code>&lt;/pre></description></item><item><title>Docs: Class Coupling</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/class-coupling/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/class-coupling/</guid><description>
&lt;p>Do not write functions that have high class coupling. This makes the code hard to maintain.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>Any procedure / trigger that has class coupling of &amp;gt; 30
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>Any procedure / trigger that has class coupling of &amp;lt;= 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\.
&lt;/code>&lt;/pre></description></item><item><title>Docs: Cyclomatic Complexity</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/cyclomatic-complexity/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/cyclomatic-complexity/</guid><description>
&lt;p>Do not write functions that have high cyclomatic complexity. This makes the code hard to maintain.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>Any procedure / trigger that has a cyclomatic complexity &amp;gt; 25, using the CC3 version mentioned in [this article][anchor0].
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>Any procedure / trigger that has a cyclomatic complexity &amp;lt;= 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\.
&lt;/code>&lt;/pre></description></item><item><title>Docs: Encapsulate Local Functionality</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/encapsulate-local-functionality/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/encapsulate-local-functionality/</guid><description>
&lt;p>Any function used local must be defined as local.&lt;/p></description></item><item><title>Docs: FINDSET FINDFIRST FINDLAST</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/findset-findfirst-findlast/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/findset-findfirst-findlast/</guid><description>
&lt;p>FINDSET, FIND('+') or FIND('-') should only be used when NEXT is used and vice versa.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>IF Cust.FIND('-') THEN
ERROR(CustIsBlockErr)
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>IF Cust.FINDFIRST THEN
ERROR(CustIsBlockErr)
&lt;/code>&lt;/pre>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>IF Cust.FINDFIRST THEN
REPEAT
...
UNTIL Cust.NEXT = 0;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>IF Cust.FINDSET THEN
REPEAT
...
UNTIL Cust.NEXT = 0;
&lt;/code>&lt;/pre></description></item><item><title>Docs: Initialized Variables</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/initialized-variables/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/initialized-variables/</guid><description>
&lt;p>Variables should always be set to a specific value, before they are used.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>PROCEDURE SetPurchLine@22(VAR CurrentPurchLine@1000 : Record 39);
VAR
Pegging@1001 : Boolean;
BEGIN
IF Pegging THEN
CurrQuantity := CurrentPurchLine.&amp;quot;Quantity (Base)&amp;quot;
ELSE
CurrQuantity := CurrentPurchLine.&amp;quot;Outstanding Qty. (Base)&amp;quot;;
END;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>PROCEDURE SetPurchLine@22(VAR CurrentPurchLine@1000 : Record 39);
VAR
Pegging@1001 : Boolean;
BEGIN
Pegging := IsPegging(CurrentPurchLine);
IF Pegging THEN
CurrQuantity := CurrentPurchLine.&amp;quot;Quantity (Base)&amp;quot;
ELSE
CurrQuantity := CurrentPurchLine.&amp;quot;Outstanding Qty. (Base)&amp;quot;;
END;
&lt;/code>&lt;/pre>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>// In the example below, the function will always return FALSE.
PROCEDURE GetItemsToPlan@22() : Boolean;
BEGIN
SETRANGE(&amp;quot;Document Type&amp;quot;,&amp;quot;Document Type&amp;quot;::Order);
...
FINDSET
END;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>PROCEDURE GetItemsToPlan@22() : Boolean;
BEGIN
SETRANGE(&amp;quot;Document Type&amp;quot;,&amp;quot;Document Type&amp;quot;::Order);
...
EXIT(FINDSET)
END;
&lt;/code>&lt;/pre></description></item><item><title>Docs: Maintainability Index</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/maintainability-index/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/maintainability-index/</guid><description>
&lt;p>&lt;a href="http://blogs.msdn.com/b/codeanalysis/archive/2007/11/20/maintainability-index-range-and-meaning.aspx">Maintainability Index&lt;/a>: Do not write functions that have a very low maintainability index. This makes the code hard to maintain.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>Any procedure / trigger that has a maintainability index &amp;lt; 20
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>Any procedure / trigger that has a maintainability index &amp;gt;= 20\.
The Maintainability Index is computed as a function:
- Lines Of Code (inverse proportional)
- the Halstead Volume
- Cyclomatic Complexity (inverse proportional).
&lt;/code>&lt;/pre>
&lt;p>More info&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://en.wikipedia.org/wiki/Halstead_complexity_measures">Halstead Volume&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.aivosto.com/project/help/pm-complexity.html">Cyclomatic Complexity&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>Any procedure / trigger that is &amp;gt; 100 lines of code
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>Any procedure / trigger that is &amp;lt;= 100 lines of code.
A full C/AL Statement counts as 1 line of code
&lt;/code>&lt;/pre></description></item><item><title>Docs: Parameter Placeholders</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/parameter-placeholders/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/parameter-placeholders/</guid><description>
&lt;p>The number of parameters passed to a string must match the placeholders.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>CannotDeleteLineErr@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.';
...
ERROR(CannotDeleteLineErr,TABLECAPTION);
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>CannotDeleteLineErr@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.';
...
ERROR(CannotDeleteLineErr);
&lt;/code>&lt;/pre>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>CannotUseThisFieldErr@1020 : TextConst 'ENU=You cannot use this field for %2 fields.';
...
ERROR(CannotUseThisFieldErr,0,Field.Class);
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>CannotUseThisFieldErr@1020 : TextConst 'ENU=You cannot use this field for %1 fields.';
...
ERROR(CannotUseThisFieldErr,Field.Class);
&lt;/code>&lt;/pre>
&lt;h3 id="heading">&lt;/h3></description></item><item><title>Docs: Static Object Invocation</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/static-object-invocation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/static-object-invocation/</guid><description>
&lt;p>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.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>LOCAL PROCEDURE Code@1();
VAR
CAJnlPostBatch@1001 : Codeunit 1103;
BEGIN
CAJnlPostBatch.Run(CostJnlLine);
END;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>LOCAL PROCEDURE Code@1();
BEGIN
CODEUNIT.RUN(CODEUNIT::&amp;quot;CA Jnl.-Post Batch&amp;quot;,CostJnlLine);
END;
&lt;/code>&lt;/pre></description></item><item><title>Docs: Unreachable Code</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/unreachable-code/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/unreachable-code/</guid><description>
&lt;p>Do not write code that will never be hit.&lt;/p>
&lt;p>It affects code readability and can lead to wrong assumptions.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>IF Type &amp;lt;&amp;gt; Type::FIELD THEN BEGIN
...
ERROR(...);
RecRef.CLOSE;
END;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>IF Type &amp;lt;&amp;gt; Type::FIELD THEN BEGIN
...
RecRef.CLOSE;
ERROR(...);
END;
&lt;/code>&lt;/pre></description></item><item><title>Docs: Unused Initialized Variables</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/unused-initialized-variables/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/unused-initialized-variables/</guid><description>
&lt;p>The value assigned to a variable must be used. Else the variable is not necessary.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>PROCEDURE AddEntities@1(FilterStr@1000 : Text[250]);
VAR
Vendor@1001 : Record 23;
Count@1002 : Integer;
BEGIN
Count := 0;
Vendor.SETFILTER(&amp;quot;No.&amp;quot;,FilterStr);
IF Vendor.FINDSET THEN
REPEAT
&amp;quot;User ID&amp;quot; := USERID;
&amp;quot;Vendor No.&amp;quot; := Vendor.&amp;quot;No.&amp;quot;;
IF INSERT THEN
Count += 1;
UNTIL Vendor.NEXT = 0;
END;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>PROCEDURE AddEntities@1(FilterStr@1000 : Text[250]);
VAR
Vendor@1001 : Record 23;
BEGIN
Vendor.SETFILTER(&amp;quot;No.&amp;quot;,FilterStr);
IF Vendor.FINDSET THEN
REPEAT
&amp;quot;User ID&amp;quot; := USERID;
&amp;quot;Vendor No.&amp;quot; := Vendor.&amp;quot;No.&amp;quot;;
IF INSERT THEN;
UNTIL Vendor.NEXT = 0;
END;
&lt;/code>&lt;/pre></description></item><item><title>Docs: Unused Variables</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/unused-variables/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/unused-variables/</guid><description>
&lt;p>Do not declare variables that are unused.&lt;/p>
&lt;p>Unused variables affect readability.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>PROCEDURE CheckPostingDate@23(CaptionEntryNo@1005 : Text[50]);
BEGIN
IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN
ERROR(DateNotAllowedErr,Caption,EntryNo)
IF PostingDate &amp;gt; MaxPostingDate THEN
MaxPostingDate := PostingDate;
END
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>PROCEDURE CheckPostingDate@23();
BEGIN
IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN
ERROR(DateNotAllowedErr,Caption,EntryNo);
IF PostingDate &amp;gt; MaxPostingDate THEN
MaxPostingDate := PostingDate;
END;
&lt;/code>&lt;/pre>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;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;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>PROCEDURE IsReturned@14(EntryNo@1002 : Integer) : Decimal;
BEGIN
EXIT(-OutboundApplied(EntryNo,TRUE) - InboundApplied(EntryNo,TRUE));
END;
&lt;/code>&lt;/pre></description></item><item><title>Docs: Variable Capacity Mismatch</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/variable-capacity-mismatch/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/variable-capacity-mismatch/</guid><description>
&lt;p>Do not assign a value to a variable whose capacity is smaller.&lt;/p>
&lt;p>It will throw an error at runtime.&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>FileName@1010 : Text[250];
...
UploadedFileName@1016 : Text[1024];
...
FileName := UploadedFileName;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>FileName@1010 : Text[1024];
...
UploadedFileName@1016 : Text[1024];
...
FileName := UploadedFileName;
&lt;/code>&lt;/pre>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>FileName@1010 : Text[250];
...
UploadedFileName@1016 : Text[1024];
...
FileName := UploadedFileName;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;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
&lt;/code>&lt;/pre>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>VAR
ExceededNumberTxt@001 : 'ENU=Warning: Exceeded number of unsent documents/requests'
Subject@1002 : Text[50];
...
BEGIN
...
Subject := ExceededNumberTxt;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>VAR
ExceededNumberTxt@001 : 'ENU=Warning: Exceeded number of unsent documents/requests'
Subject@1002 : Text[100];
...
BEGIN
...
Subject := ExceededNumberTxt';
&lt;/code>&lt;/pre></description></item><item><title>Docs: WITH Scope Name Collision</title><link>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/with-scope-name-collision/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alguidelines.dev/docs/navpatterns/3-cal-coding-guidelines/design/with-scope-name-collision/</guid><description>
&lt;p>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.&lt;/p>
&lt;p>&lt;strong>Given that&lt;/strong> &lt;br>
&amp;ldquo;Contract Type&amp;rdquo; 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?&lt;/p>
&lt;p>Bad code&lt;/p>
&lt;pre>&lt;code>PROCEDURE InsertData@1(&amp;quot;Contract Type&amp;quot;@1000 : Option...);
...
BEGIN
...
WITH ServiceContractHeader DO BEGIN
...
DimMgt.InsertServContractDim(...,&amp;quot;Contract Type&amp;quot;,&amp;quot;Contract No.&amp;quot;,0,...);
END;
&lt;/code>&lt;/pre>
&lt;p>Good code&lt;/p>
&lt;pre>&lt;code>PROCEDURE InsertData@1(ContractType@1000 : Option...);
...
BEGIN
...
WITH ServiceContractHeader DO BEGIN
...
DimMgt.InsertServContractDim(...,ContractType,&amp;quot;Contract No.&amp;quot;,0,...);
END;
&lt;/code>&lt;/pre></description></item></channel></rss>