Adding Procedure Parameters Without Breaking Backwards Compatibility in VBA

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