Call keyword - deprecated or not
I try to avoid the Call
(and thus, it is depreciated for me) for the following reason - in #VBA I consider passing a variable in parenthesis as a way to overrun the standard ByVal
/ByRef
specification of the parameters. What do I mean? Consider this example:
Public Sub TestMe()
Dim var1 As Long: var1 = 1
Dim var2 As Long: var2 = 1
IncrementByVal (var1)
IncrementByRef (var2)
Debug.Print var1, var2
End Sub
Public Function IncrementByVal(ByVal a As Variant) As Variant
a = a + 100
IncrementByVal = a
End Function
Public Function IncrementByRef(ByRef a As Variant) As Variant
a = a + 100
IncrementByRef= a
End Function
As you probably see, both var1
and var2
return 1
, while the var2
should be 101
, as far as it is ByRef
. The Call
-word kind-of improves this "feature" in VBA, but it becomes too complicated to remember when the parenthesis are overriding the ByRef
and when not, when reading code. Thus, having 3 cases is quite a lot:
Public Sub TestMe()
Dim var1 As Long: var1 = 1
Dim var2 As Long: var2 = 1
Dim var3 As Long: var3 = 1
Dim var4 As Long: var4 = 1
Dim var5 As Long: var5 = 1
Dim var6 As Long: var6 = 1
IncrementByVal (var1) '1
IncrementByRef (var2) '1
IncrementByVal var3 '1
IncrementByRef var4 '101
Call IncrementByVal(var5) '1
Call IncrementByRef(var6) '101
Debug.Print var1, var2
Debug.Print var3, var4
Debug.Print var5, var6
End Sub
Public Function IncrementByVal(ByVal a As Variant) As Variant
a = a + 100
IncrementByVal = a
End Function
Public Function IncrementByRef(ByRef a As Variant) As Variant
a = a + 100
IncrementByRef = a
End Function
Consistency is King
Whether you're calling a Sub
or a Function
makes no difference whatsoever. What matters is consistency, and that's what Call
-lovers claim using the keyword brings. Consistency with whether or not parentheses are required around the argument list.
So instead of this simple-as-it-gets implicit call statement:
MsgBox "I'm a function but you're discarding my return value, so no parentheses."
We get things like this:
MsgBox ("I'm a function but you're discarding my return value, and this call looks weird.")
And I've yet to see the Call
actually being used with any kind of actual consistency:
Call MsgBox("I'm a function but you're discarding my return value, so I have a Call keyword.")
Call MyProcedure(42)
Call ActiveSheet.Range("A1:A10").Clear
Call Beep
Call CallByName(obj, "SomeMethod", VbCalType.VbMethod)
Used consistently, Call
quickly becomes obnoxious, clearly redundant, and slows down reading, because the eyes can't help but stop on the keyword, and then the brain goes "oh hey watch out, we're calling something here". I suppose at one point you just stop seeing it, and just feel like something's off if it's missing.
The overwhelming majority of every single executable statement is going to be a call to something, somewhere, at some abstraction level - using Call
consistently makes the language even more heavily bulky than it already is.
Unless Call
isn't really about consistency, more about being able to easily see my own user procedure calls... which at this point is just grasping at straws to legitimize an archaic construct that serves no purpose whatsoever: there is no legitimate use for the Call
keyword.
This is the only "legit" use case:
Public Sub Test()
DoSomething: DoSomethingElse
'vs.
'Call DoSomething: Call DoSomethingElse
End Sub
Private Sub DoSomething() '<~ dead code. can you see why?
End Sub
Private Sub DoSomethingElse()
End Sub
The Call
keyword disambiguates a LineLabel:
from a ParameterlessProcedureCall
followed by an :
instruction separator. Only problem is, :
instruction separators are great for golfing and cramming as much executable code as possible on a single line of code ...they are terrible for readability. Besides everybody agrees that a NEWLINE
should follow a ;
in any semicolon-terminated language, even though it makes perfect sense for the compiler to ignore the line jump.
We write code for people to read and maintain, not just for compilers to build and run.
Deprecated? Says who?
Says me and my ducky.
I'm 100% certain I've read in the Official docs at one point in my life, that the keyword was obsolete. Heck, it's even specified as being redundant. Languages evolve, VBA is no exception - even with mind-blowingly incredible levels backward-compatibility and after two decades without any kind of significant changes, the language - or rather, its practices are still fluid, even if its maintainers have retired vowed to just let it be.
Call
isn't the only deprecated token in VBA, but for questionable subjective reasons not unsimilar to what people use to justify clinging to the formidable mistake that Systems Hungarian notation was in this century, its roots run deep.
Where are the defenders of the Rem
comment marker? Why is While...Wend
still a thing when Do While...Loop
supersedes it? Is anyone raising errors with the Error
statement rather than through Err.Raise
? Outputting error messages with Error$
rather than through Err.Description
? Declaring variable types with the ever-so-legible $&%^!#
type hints? Who writes On Local Error
? Why use Global
for things that are really Public
?
And if explicit Call
isn't obsolete and "makes the code more readable", then why aren't the same people using explicit Let
statements for value assignments for the exact same explicitness/clarity reasons?
I think it's well past time to rewrite what the best practices are, and leave Call
in the past, along with Let
, Hungarian Notation, and walls-of-declarations at the top of procedures. Every single programming community did this already - only the VBA community is still holding on to the "best practices" from 30 years ago, for brand new code, not just legacy stuff written in another era. I suspect it's very much possible that VBA's "dread score" has a lot to do with that, even without taking the bare-bones IDE into account.
I'd love to be able to pull a reference from fellow Microsoft MVP old-timers Rob Bovey and Stephen Bullen, and say "here, see, page 172 it says the call keyword is obsolete" (this answer seems to mention a "two inch thick book on VBA basically says don't use it unless you want to use the Find feature of the VBE to easily find calls in large projects"), so this might be it, but in any case at the time these gurus essentially defined what the best practices were, "refactoring" was a foreign word, "unit testing" was an extreme programming crazy idea - the entire programming landscape has enormously evolved in the past 20 years, but VBA stood still. Time to end this, and move on forward.
Speaking of moving forward...
"It makes it easier to migrate to VB.NET"
Allegedly, using Call
statements make it "easier" to port the code to .NET, because VB.NET will want the parentheses everywhere. Respectfully, that's bullocks. First because what you'll want to port your VBA code to isn't VB.NET but much more likely TypeScript, if any kind of porting is ever actually going to happen. If you're manually copying VBA code and "translating it" to get it to compile in the target language, you'll quickly find that not having to deal with parentheses is a very marginal perk, since literally everything else needs to be rewritten, ...including dropping the Call
tokens.
Writing small, specialized procedures that do very few things if ever more than a single one, leveraging abstraction levels and classes/objects, reducing coupling between modules, improving cohesion within modules, having your code's behavior covered and documented with thorough unit tests, that will help you port your VBA code faster, and ensure the ported code works identically. Call
and parentheses are just an excuse to keep a bad habit going.
I frequently use Call
when I'm refactoring code or cutting new code I'm not yet sure of. To explain, using Call
requires brackets around the arguments and so does returning a value from a function. I might want to return a value from a function, or I might want to pass an argument by reference (ByRef
)
Sub Test()
Dim v
'* Call requires brackets
Call NotSureIfThisWillEndUpAsASubOrAFunction(1, 2)
'* return a value from a Function then need brackets
v = NotSureIfThisWillEndUpAsASubOrAFunction(1, 2)
'* if always a sub then no brackets required
WillAlwaysBeASub 4, 5
'* but is this really so ugly, why deprecate this?
Call WillAlwaysBeASub(4, 5)
End Sub
Function NotSureIfThisWillEndUpAsASubOrAFunction(a, b)
End Function
Sub WillAlwaysBeASub(c, d)
End Sub
EDIT: I think using brackets all the time (which means using Call
as a keyword for Subs) means less time hopping around the code taking brackets out and then putting them back in upon change of mind.