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