Coding Best Practices Part 1

Wednesday, 17 February 2021 by Gavin Huet

This blog outlines coding practices that any professional developer using the finPOWER Connect API should adhere to.

finPOWER Connect is written entirely in VB.NET. All sample scripts are also written in VB.NET. Although C# is an option, Intersoft Systems does not provide support for this.

In this first blog we will look at coding practices.


Related Blogs


More information is available in the finPOWER Connect Programming Guide.

Top Best Practices

Always check return values

Many methods return either TRUE or FALSE depending on whether they have succeeded. This value should always be checked. The more complicated and nested the code is, the harder it will be to track errors if you don't check return values.

Private Function Account_AddPaymentArrangement(accountPk As Integer,
                                               arrangementDate As Date,
                                               arrangementByWhom As String,
                                               arrangementType As String,
                                               arrangementReason As String,
                                               paymentCycle As String,
                                      Optional paymentNextDate As Date = Nothing,
                                      Optional paymentOverride As Decimal = 0) As Boolean

  Dim AccountPayArrangementAdd As finAccountPayArrangementAdd
  Dim Success As Boolean

  ' Assume success
  Success = True

  ' Initialise
  AccountPayArrangementAdd = finBL.CreateAccountPayArrangementAdd()

  ' Create Payment Arrangement
  With AccountPayArrangementAdd
    ' Load Account
    Success = .AccountLoadPk(accountPk)

    ' Clear existing Promises
    If Success Then
      Success = .PromisesClear()
    End If

    If Success Then
      ' Update Properties
      .ArrangementByWhom = arrangementByWhom
      .ArrangementDate = arrangementDate
      .ArrangementReason = arrangementReason
      .ArrangementType = arrangementType
      .OverdueHold = True
      .PrintAdvice = False

      ' Update Calculation
      With .Calculation
        If Len(paymentCycle) <> 0 Then .PaymentCycle = paymentCycle
        If paymentNextDate <> Nothing Then .PaymentNextDate = paymentNextDate
        If paymentOverride <> 0 Then .PaymentRegularOverride = paymentOverride
      End With

      ' Calculate
      Success = .Calculate()
    End If

    ' Commit Payment Arrangement
    If Success Then
      Success = .ExecuteCommit()
    End If
  End With

  ' Return Success
  Return Success

End Function

Generally, if a method returns FALSE, an error message will have been set. Exceptions to this rule include methods used to check for the existence of values, for example:

  • Exists
  • ExistsPk
  • HasValues

Certain properties, usually collections, are loaded on demand, finAccount.Transactions for example.

Accessing these collections directly is fine in most situations, for example to display a list of transactions. However, for situations that rely on the collection to have been loaded correctly, the property's corresponding 'Load' method should first be called and the return value checked.

Dim Account As finAccount
Dim Success As Boolean

' Assume Success
Success = True

' Check the Transactions Load
If Success Then
  Success = Account.TransactionsLoad()
End If
  
If Success Then
  ' Do Something
End If
  
' Return Success
Return Success

Database Transactions

Database Transactions can have an affect on performance and data integrity. Database Transactions are used to ensure all related operations are completed or undone as a whole.

  • Long running tasks within a Database Transaction will affect the performance of the system as it will lock database resources for the length of the task. This stops other users from accessing these resources.
  • Calls to external services within a Database Transaction can result in data integrity issues if the Database Transaction is rolled back. This is because the external service may have been updated but the finPOWER Connect database doesn't reflect this.
  • Calls to User Interface components, such as a Message Box, within a Database Transaction may cause the Script to stop and wait for user interaction which in turn will affect performance as the database resources will be locked until the user interaction has been completed.

For example, never post payments to an external banking API and update data in finPOWER Connect. If the script fails and rolls back the Database Transaction the external banking API may have already transferred money but finPOWER Connect hasn't (as it was undone) - and finPOWER Connect will send the duplicate payment again.

Dim Success As Boolean

' Assume Success
Success = True

' Begin Transaction
If finBL.Database.TransactionBegin() Then
  ' Call External Service that updates state
  Success = CallExternalService()

  ' Update flag in finPOWER Connect to day it is done, but it fails
  If Success Then
    Success = CallMethodThatFails()
  End If

  ' Commit or Rollback Transaction
  If Success Then
    finBL.Database.TransactionCommit()
  Else
    ' Transaction rollback leaves the external service with data in a different state
    finBL.Database.TransactionRollback()
  End If
Else
  Success = False
End If

' Return Success
Return Success  

User Interface Components

Calls to User Interface objects, such as a Message Box, must never be used in Database Transactions or unattended Scripts as the Script will not be able to complete until the user interaction has been completed.

An example of a call to a User Interface object, in this case a Message Box, within a Database Transaction. This will cause performance issues as the Database Transaction will lock tables until the user interaction is completed:

Dim Success As Boolean

' Assume Success
Success = True

' Begin Transaction
If finBL.Database.TransactionBegin() Then
  ' Do something
  
  ' Display Message Box which will hold the transaction lock
  MsgBox("Hello World")
  
  ' Do something
  
  ' Commit or Rollback Transaction
  If Success Then
    finBL.Database.TransactionCommit()
  Else
    finBL.Database.TransactionRollback()
  End If
Else
  Success = False
End If

' Return Success
Return Success

Global Collections

finPOWER Connect preloads most Admin files into "Global Collections" which are held in memory. Wherever possible use a Global Collection rather than loading information a second time from the database. As well as improving performance, using Global Collections makes coding simpler and more compact.

The following code illustrates loading an object:

Dim Element As finElement
Dim Success As Boolean

' Assume Success
Success = True

' Load Element
If Success Then
  ' Create Element
  Element = finBL.CreateElement()

  Success = Element.Load("FEE")
End If

' Process
If Success Then
  If Element.Active Then
    ' Do Something
  End If
End If

' Return Success
Return Success

The following code illustrates using the Global Collection:

Dim Success As Boolean

' Assume Success
Success = True

' Check that the Element exists and it is active
If finBL.Elements.Exists("FEE") AndAlso finBL.Elements("FEE").Active Then
  ' Do Something
End If

' Return Success
Return Success

Option Strict

Option Strict requires that all declared variables have a data type specified and will restrict implicit data type conversions to only widening conversions and also disallow late binding.

Normally, setting one variable to another variable of a different type indicates a programming error. However, Visual Basic allows conversions of many data types to other data types. This can result in:

  • Data loss - this is when the value is converted to a data type with less precision or a smaller capacity;
  • Run-time error - this occurs when a narrowing conversion fails.

Option Strict ensures a compile-time notification of these narrowing conversions so that they can be avoided.

The default is Off therefore you must turn it on to use it, this can be achieved by adding the following code to the top of your Script code:

Option Strict On

Narrowing data type conversions can cause a compile-time error:

Dim IntegerVariable As Integer
Dim LongVariable As Long
  
' Set a Long variable
LongVariable = Long.MaxValue
  
' Set Integer variable to value of Long variable
IntegerVariable = LongVariable

' This results in a narrowing conversion
' With Option Strict on a compile error is encountered: 
'   Option Strict On disallows implicit conversions from 'Long' to 'Integer'

Option Explicit

Option Explicit requires that all variables are declared. If you attempt to use an undeclared variable name an error will occur at compile time.

To turn it on add the following code to the top of your Script code:

Option Explicit On

Variables must have a data type specified:

' Use a variable that is not declared
LongVariable = Long.MaxValue
  
' With Option Explicit on a compile error is encountered: 
'   'LongVariable' is not decalred. It may be inaccessable due to its protection level.

versus

Dim LongVariable As Long

LongVariable = Long.MaxValue