alguidelines/docs/navpatterns/3-cal-coding-guidelines/design/index.xml
2023-09-22 14:37:02 +00:00

316 lines
No EOL
16 KiB
XML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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</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(&amp;rsquo;+&amp;rsquo;) or FIND(&amp;rsquo;-&amp;rsquo;) 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>