Personal coding guidelines
A collection of recommendations, conventions and some scaffolding, for naming and crafting solutions in classic (VB5/6) and Visual Basic for Applications.
I wrote the first version of these guidelines back in 2014.
I know every one of them is debatable and every developer has his or her own style and convictions.
Feel free to use them as they are, or customize and build your own, it is a motivating exercice that may help or prevent consistency issues from the beginning.
Naming conventions
The”Hungarian notation” principle basically consists in using a set of standard prefixes (all in lower case), before each variable name to indicate its scope and type. Use it.
Exceptions to this rule, or when it may not be appropriate to use the Hungarian notation to name things, are indicated along, and there’s a paragraph about those exceptions later.
Do not use Hungarian for constants (and there’s your first exception).
All constant names are in upper case and, if they consist of multiple “words”, they may use an underscore to separate them.
Examples:
'Like API constants:
Private Const SW_MAXIMIZE As Long = 3&
Private Const SW_NORMAL As Long = 1&
Private Const SW_HIDE As Long = 0&
Private Const SW_SHOW As Long = 5&
'And we may do:
Private Const COLFLAG_DATETIME As Long = 1&
'Which I prefer over:
Private Const COL_FLAG_DATETIME As Long = 1&
Examples:
'DON'T DO THAT:
Private Const SW_MAXIMIZE = 3
'BUT INSTEAD, DO:
Private Const SW_MAXIMIZE As Long = 3&
Note the ampersand (“&”) after the “3”, that matches the type of the constant (here a Long).
As long as we’re talking about where a variable is defined inside a project, scope in Visual Basic is simple. A variable can be global to the entire application, or local to a class module or standard module (or any other document provided by the development environment like a Form or a Sheet), or local to a function (or Sub).
Prefix | Scope |
---|---|
g | Application global scope |
m | Class, module or other document (Form, Report, Sheet, etc…). “m” stands for “Member” variable. |
(none) | Local to the current function (or Sub), or see [Exceptions] |
There is no underscore following this 1 letter prefix.
Visual Basic data type | Prefix |
---|---|
Integer | “i” |
Long | “l” (minus L). Although in rare cases I don’t mind loosely using “i” when confusion wouldn’t matter so much. |
Variant | “v” |
String | “s” (and not that horrible “str”) |
Boolean | “f” (stands for “flag”, while I use “b” for the Byte data type) |
Byte | “b” |
Single | “sng” |
Double | “dbl” |
Currency | “c” or “cur” |
Object | “o” (and not “obj”) |
Date | “dt” (I use the Date datatype in very rare occurrences, I mostly use Variants for dates) |
Collection | “col” |
There’s nothing wrong in using one letter variable names for Integer or Long indices (or indexes), whether it is in loops (For, Do, While, Repeat, …) or for index access to an item. But it may help using longer indices names (like iCustomer, iLine, iCol) when it helps disambiguate heavily packed code. For iteration on other data types (object reference, interface, structures, etc…), avoid one letter iterator variable names.
#personal: Use “i” for rows, and “j” for columns, then “k” for anything else. If nesting more, think of using more explicit variable names.
UI element | Prefix |
---|---|
Form | “frm” |
Report | “rpt” |
Form used as a (modal or modeless) dialog | “frm” + name + “Dialog”, (eg. frmLoginDialog) |
Subform (Access) | “sfrm” |
Class module as an interface | “I” |
Textbox | “txt” |
Combobox | “cbo” |
Listbox | “lst” |
Button | “cmd” |
Label | “lbl” |
Tab control | “tab” |
Checkbox | “chk” |
Option button | “opt” |
Scrollbar | “hsb” or “vsb” (horizontal, respectively vertical) |
Data type | Prefix |
---|---|
Type definition (structures) | “T” |
Object of any other class | “o” or a 2 or 3 letter prefix for the class, like “cn” for and ADODB connection, or “rs” for a Recordset, etc… |
Standard module | “M” |
Class module | “C”. This is unless you expose by any means the class outside of your project or database, in which case you should use a non prefixed class name (also see [Exceptions]). |
Class module, used as an interface | “I” (uppercase “i”) |
Defined Type | “t” or “tag” |
Inside a type definition, use Hungarian notation for member variables, example:
'Inside the CQueue class module (https://francescofoti.com/...)
Private Type TQHeader
iQCount As Integer
iQBack As Integer
iQFront As Integer
iMaxCapacity As Integer
End Type
'The queue header holds runtime information about the queue
Private mTHeader As TQHeader
Every function / sub parameter variable is prefixed with a “p”. That’s another instance of an invaluable indicator in the body of the function’s code that allows to quickly identify if a variable is a parameter that has been passed to the function, among the ones that have been declared in its body. Without this prefix, it may be sometimes very difficult to distinguish between the origin of variables while reading the function’s code.
#personal: You’ll also quite commonly see the prefix “Ret” inserted after the variable scope and type prefix, when a function parameter variable (ByRef) is going to be modified in a meaningful way for the caller, by the function.
I also insert “New” after “Ret” if the (ByRef) parameter variable is an object that will be created inside the function.
'This function expects a reference to an existing CList instance in poRetList,
'and it will modify the passed object, but will not create it.
'Note that I crafted the "lst" prefix for a CList object reference variable in plstParams.
'I also crafted the "cn" prefix for an ADODB connection object reference.
'But I kept the generic "o" preifx in poRetList; it's OK to mix the two as they come.
Public Function ADOGetSnapshotList( _
ByRef pcnDatabase As ADODB.Connection, _
ByRef psSQL As String, _
ByRef poRetList As CList, _
Optional ByRef plstParams As CList
) As Boolean
'etc...
End Function
'Source: https://www.tek-tips.com/viewthread.cfm?qid=1060175
'Restyled and modified to populate a CList.
'Returns True if successful or False and sets the module error context
'Returned list (poRetNewList) will have 2 columns:
' PaperNumber : Integer
' PaperName : String
'Sorted on PaperNumber
'Note that here I use the more generic "o" prefix in poRetNewList again.
Private Function GetPaperList( _
ByVal psPrinterName As String, _
ByRef poRetNewList As CList _
) As Boolean
Dim lPaperCount As Long
Dim lCounter As Long
Dim hPrinter As Long
'etc...
End Function
Every array variable is prefixed with an “a”, after the scope letter prefix, examples:
'A global array of 250 strings
Public gasLabels(1 To 250) As String
'A local array of strings
Private Sub Foo()
Dim asUnit(1 To 5) as String
End Sub
'In a standard module
Private masText(1 To 100) As String
Enumeration definitions are prefixed with a minus “e”, as are the enumeration value names. Try to choose value names that can tip enough about what type of enumeration the values refer to, to mitigate confusion. In the sample below, the “eLeft”, “eCenter” and “eRight” symbolic names, may still be slightly ambiguous; this is acceptable if there is no near possibility of ambiguity.
Examples:
'We're inside the CCGrid class module (a [C]ustom, [C]haracter (or [C]onsole) based [Grid])
Public Enum eGridViewColAlign
eLeft
eCenter
eRight
End Enum
Public Enum eGridViewCellWrap
eWrapNone
eWrapText
eFoldText
End Enum
Public Enum eTableChar
eCharE_W
eCharN_S
eCharS_E
eCharS_W
eCharN_E
echarN_W
eCharS_E_W
echarN_E_W
echarN_S_E
echarN_S_W
eCharEllipsis
echarN_S_E_W
End Enum
Private masChar(eCharE_W To echarN_S_E_W) As String
'Sample usage (focus on enum usage, don't worry about the CCGrid class):
Set oGrid = New CCGrid
fOK = ADOGetSnapshotList(cnData, SQL, lstData) 'Get some db data
oGrid.FormatCols "Title::10;ContactID:#:3;Zip:6:right;" & _
"City:10;Address:22"
Set oGrid.DataList = lstData
'use of enums; full qualification "eGridViewCellWrap." is not required.
oGrid.ColWrap(lstData.ColPos("Address")) = eGridViewCellWrap.eWrapText
oGrid.ColWrap(lstData.ColPos("Mnemonic")) = eFoldText
oGrid.ColWrap(lstData.ColPos("Floor")) = eFoldText
Let’s assume that when we refer to functions, we’re also talking about procedures (sub).
When crafting a standard module to handle a specific set of tasks as an API, prefix all the public functions in the module with more or less one to five uppercase characters that qualify the module where they belong: the module mnemonic.
Don’t prefix the private functions.
Example:
Module name is: MADOAPI (which I prefer over MAdoApi as ADO and API are rarely seen in lower case).
The chosen module mnemonic is “ADO”, it will prefix each module member function.
Sample of some of the functions signatures, prefixed by “ADO”:
Public Function ADOGetAccessConnString(ByVal psDatabasePathname As String) As String
End Function
Public Function ADOOpenConnection( _
ByRef psConnString As String, _
ByRef psDatabase As String _
) As ADODB.Connection
End Function
Public Function ADOExecSQL( _
pcnExecute As ADODB.Connection, _
ByRef psSQL As String, _
Optional ByRef plRetAffectedCt As Long_
) As Boolean
End Function
If the module is just a collection of helper functions that are logically grouped together in the module, don’t prefix the module’s member function with a module mnemonic.
Example:
Module name is: MStrings (Not an API, a collection of functions helping with string manipulations). No Prefix:
Public Function ssprintf(ByRef psModel As String, ParamArray pavParams()) As String
End Function
Function UUCase(ByVal psSource As String) As String
End Function
(You can find articles explaining both these functions on my personal website)
Do not use Hungarian notation for variable names, in these two cases:
1) Variables used as Public properties in a class module:
This happens when a variable, declared as Public, in a class module, is not wrapped with get/let/set property accessors, but is directly exposed (non managed R/W/Set).
Sample, in which we see a part of the declaration section of a class module:
'Extract from the header section (declarations) of CConsoul(.cls)
Public FontName As String
Public FontSize As Integer
Public WindowTitle As String
Public BackColor As Long
Public ForeColor As Long
Public MaxCapacity As Integer
Public WithFrame As Boolean
Public RefreshOnAutoRedraw As Boolean
2) Global objects:
'In the declaration section of a *standard* module.
Public MyApp as New CMyApp 'And not "goMyApp" or something like "gappMyApp"
'Then, as we may use an ini file, it could be a public property in CMyApp:
Dim asSectionName() As String
Dim lSectionCt As Long '"Ct" stands for "Count", that's another personal habit
lSectionCt = MyApp.IniFile.GetSectionNames(asSectionName())
'Error context
Private mlErr As Long
Private msErr As String
Private msErrCtx As String
Add the following functions, prefixing them with your module mnemonic.
As an example, they’re here prefixed with “ADO”:
Private Sub ClearErr()
mlErr = 0
msErr = ""
msErrCtx = ""
End Sub
Private Sub SetErr(ByVal psErrCtx As String, ByVal plErr As Long, ByVal psErr As String)
mlErr = plErr
msErr = psErr
msErrCtx = psErrCtx
End Sub
Public Function ADOLastErr() As Long
ADOLastErr = mlErr
End Function
Public Function ADOLastErrDesc() As String
ADOLastErrDesc = msErr
End Function
Public Function ADOLastErrCtx() As String
ADOLastErrCtx = msErrCtx
End Function
' Class error context
Private mlErr As Long
Private msErr As String
Private msErrCtx As String
Private Sub ClearErr()
mlErr = 0
msErr = ""
msErrCtx = ""
End Sub
Private Sub SetErr(ByVal psErrCtx As String, ByVal plErr As Long, ByVal psErr As String)
mlErr = plErr
msErr = psErr
msErrCtx = psErrCtx
End Sub
Public Property Get LastErr() As Long
LastErr = mlErr
End Property
Public Property Get LastErrDesc() As String
LastErrDesc = msErr
End Property
Public Property Get LastErrCtx() As String
LastErrCtx = msErrCtx
End Property
Public Function DoSomething() As Boolean
Const LOCAL_ERR_CTX As String = "DoSomething"
On error goto DoSomething_Err
ClearErr
Dim oAnyRef As Object
Set oAnyRef = CreateAnObject()
'Actually do something...
DoSomething = True
DoSomething_Exit:
Set oAnyRef = Nothing
Exit Function
DoSomething_Err:
Seterr LOCAL_ERR_CTX, Err.Number, Err.Description
Resume DoSomething_Exit
End Function
(end of VB/A coding guidelines)
Recent Comments