Coding Best Practices Part 1
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
- Never perform long running code or call external services within a Database Transaction
- Never use message boxes or any other User Interface components in Scripts that do not expose the User Interface layer, and never within a Database Transaction
- Always (where possible) use Global Collections rather than loading an object
- Option Strict should be set on
- Option Explicit should be set on
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