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 (
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.
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
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
The key point here is that any new optional parameters must always be added to the end of the procedure signature.
Buttons parameter comes before
Title in the MsgBox definition. By reversing them in our
WarnUser procedure we end up with an inconsistency between
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