Adding Procedure Parameters Without Breaking Backwards Compatibility in VBA
Sometimes you need to add a parameter to a Function or Sub in VBA. But you don't need to break all of your calling code to do it.
Making a commitment to never breaking backwards compatibility within your code will change your life as a developer.
- It allows you to safely reuse code
- It reduces the accumulation of technical debt
- It reduces the need for refactoring calling code
There are several techniques you can use to avoid breaking backwards compatibility in your code. I discuss one such technique below.
Safely Changing Procedure Signatures
A procedure (Function
, Sub
, Property
, etc.) signature is the procedure's declaration line, which comprises the procedure name, its required and optional arguments, and–where applicable–the return type.
Carelessly changing a procedure signature is a good way to break your calling code. But, as your code grows over time, you may want to be able to pass additional arguments to your procedure. So how can you allow passing additional arguments without breaking the calling code?
The solution to the problem is to use optional parameters.
Using Optional Parameters to Avoid Breaking Backwards Compatibility
Let's go through a quick example.
Starting Point
We start with a subroutine named WarnUser
. This routine shows a warning message in a message box. It's an example of a convenience function–a short procedure that wraps a bit of functionality into a more readable form.
Sub WarnUser(Msg As String)
MsgBox Msg, vbExclamation, "Be Warned!"
End Sub
Let's add some calling code:
Sub Foo()
WarnUser "You are about to Foo."
'Do Foo stuff
End Sub
Adding a Second Parameter
Now, let's say you want to call WarnUser
from a different place in your code but you want to customize the title to something other than, "Be Warned!" You could do this:
Sub WarnUser(Msg As String, Title As String)
MsgBox Msg, vbExclamation, Title
End Sub
And call it like so:
Sub Bar()
WarnUser "You are about to Bar.", "!!DANGER!!"
'Do Bar stuff
End Sub
That would work, but it would break your calling code in Foo
. You would have to go back and change that:
Sub Foo()
WarnUser "You are about to Foo.", "Be Warned!"
'Do Foo stuff
End Sub
That doesn't seem terrible, but what if you called WarnUser
in fifty other places? That would require refactoring code all over the place and you would have to be really careful to avoid typos and make sure you didn't miss anything. Sounds like a recipe for disaster.
A much better approach is to make the new parameter optional and make its default value equal to what was previously hard-coded within the procedure:
Sub WarnUser(Msg As String, _
Optional Title As String = "Be Warned!")
MsgBox Msg, vbExclamation, Title
End Sub
With this approach, you can still use the extra parameter in Bar
, but you don't need to make any changes to Foo
:
Sub Foo()
WarnUser "You are about to Foo."
'Do Foo stuff
End Sub
Sub Bar()
WarnUser "You are about to Bar.", "!!DANGER!!"
'Do Bar stuff
End Sub
Adding a Third Parameter
To drive the point home, let's assume your new procedure Baz
requires showing the red "X" icon instead of the exclamation point icon. You would extract out the Buttons
value into an optional parameter just as you did with the Title
parameter.
The key point here is that any new optional parameters must always be added to the end of the procedure signature.
The Buttons
parameter comes before Title
in the MsgBox definition. By reversing them in our WarnUser
procedure we end up with an inconsistency between WarnUser
and MsgBox
. If we had added both parameters at the same time, it would have made sense to match the MsgBox
parameter order. However, since we already have calling code that depends on Title
being the second parameter, we must leave it in place.
Backwards compatibility always trumps consistency.
Sub WarnUser(Msg As String, _
Optional Title As String = "Be Warned!", _
Optional Buttons As VbMsgBoxStyle = vbExclamation)
MsgBox Msg, Buttons, Title
End Sub
Sub Foo()
WarnUser "You are about to Foo."
'Do Foo stuff
End Sub
Sub Bar()
WarnUser "You are about to Bar.", "!!DANGER!!"
'Do Bar stuff
End Sub
Sub Baz()
WarnUser "You are about to Baz.", , vbCritical
'Do Baz stuff
End Sub
Image by PublicDomainPictures from Pixabay