Skip to main content
Code Review

Return to Answer

replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

Procedure names should always be verbs - they are things your program does, that's why. A better name might be IsInCollection - the only better alternative I can think of would be to implement your own collection/enumerable class implement your own collection/enumerable class, and make it have a Contains, or ContainsKey method... that might be overkill if this is the only time you're ever going to need to find a key in a collection. On the other hand, having a "toolbox" with frequently used class modules ready to import into any VBA project, can be handy.

Procedure names should always be verbs - they are things your program does, that's why. A better name might be IsInCollection - the only better alternative I can think of would be to implement your own collection/enumerable class, and make it have a Contains, or ContainsKey method... that might be overkill if this is the only time you're ever going to need to find a key in a collection. On the other hand, having a "toolbox" with frequently used class modules ready to import into any VBA project, can be handy.

Procedure names should always be verbs - they are things your program does, that's why. A better name might be IsInCollection - the only better alternative I can think of would be to implement your own collection/enumerable class, and make it have a Contains, or ContainsKey method... that might be overkill if this is the only time you're ever going to need to find a key in a collection. On the other hand, having a "toolbox" with frequently used class modules ready to import into any VBA project, can be handy.

labels aren't indented
Source Link
Mathieu Guindon
  • 75.5k
  • 18
  • 194
  • 467
Private Function IsInCollection(ByVal col As Collection, ByVal key As String) As Boolean
 Dim result As Boolean
 On Error GoTo CleanFail
 'function logic goes here
CleanExit:
 IsInCollection = result
 Exit Function
CleanFail:
 result = False
 Resume CleanExit
End Function
Private Function IsInCollection(ByVal col As Collection, ByVal key As String) As Boolean
 Dim result As Boolean
 On Error GoTo CleanFail
 'function logic goes here
CleanExit:
 IsInCollection = result
 Exit Function
CleanFail:
 result = False
 Resume CleanExit
End Function
Private Function IsInCollection(ByVal col As Collection, ByVal key As String) As Boolean
 Dim result As Boolean
 On Error GoTo CleanFail
 'function logic goes here
CleanExit:
 IsInCollection = result
 Exit Function
CleanFail:
 result = False
 Resume CleanExit
End Function
Source Link
Mathieu Guindon
  • 75.5k
  • 18
  • 194
  • 467

@RubberDuck gave a pretty awesome review here, I'm just going to (削除) cover (削除ここまで) dissect the InCollection function, which was left out.

I like that you are sticking a comment at the top of every procedure - that's very good practice. One little nitpick about this one though:

'Returns boolean true if an object is within a collection

The value true is always going to be a Boolean value, and since the function's signature already specifies a Boolean return type, it's best to just leave it out. Also the comment is a little bit misleading - it doesn't reaturn true if an object is within a collection: it returns true when a specified key exists in a specified collection. Hence I'd rephrase it as such:

'Returns True when specified key exists in specified collection.
Public Function InCollection(col As Collection, key As String) As Boolean

Parameters are implicitly passed ByRef, which means the function is empowered with the ability to change the reference of col and the value of key before the execution flow returns to the caller: even if you're not doing that, it's best to pass parameters ByVal, and when you intend parameters to be passed by reference, to pass them explicitly ByRef.

Procedure names should always be verbs - they are things your program does, that's why. A better name might be IsInCollection - the only better alternative I can think of would be to implement your own collection/enumerable class, and make it have a Contains, or ContainsKey method... that might be overkill if this is the only time you're ever going to need to find a key in a collection. On the other hand, having a "toolbox" with frequently used class modules ready to import into any VBA project, can be handy.

But I digress. Let's dive into the procedure's body.

 Dim var As Variant

I like that you're declaring the Variant type explicitly. As you may know, all VBA variables are of Variant type if you don't specify a type - being explicit is always a good thing.

The name isn't very good though: var is to Variant such as str is to String - and both are awful meaningless variable names. Naming things after their intent rather than their type usually helps with meaning. In this case value might be a better name.

 Dim errNumber As Long

Don't need. I'll get back to that one.

 InCollection = False
 Set var = Nothing

Don't need either. Any Boolean is False by default, so the function would return False if you never assigned its return value. It's good that you're explicitly assigning a value though, but assigning the return type's default value as the first executable line of code in the function feels wrong.

The type name of an unassigned Variant is Empty, and IsObject(var) would return False before that assignment. By setting it to Nothing, you have changed its type name to Nothing, and IsObject(var) would return True.

Since at this point we don't know yet exactly what we're going to get from the collection, we might as well leave it alone and let it be an Empty value.

 Err.Clear

If executable code above that line raised an error, this line wouldn't execute, because execution would jump right out of the procedure since no On Error statement was encountered before. Hence, Err.Number can only be 0 at this point, and calling Err.Clear is useless. This line has no effect whatsoever, and can be removed.

 On Error Resume Next
 var = col.Item(key)
 errNumber = CLng(Err.Number)
 On Error GoTo 0

Now you're treating var (which the VBA runtime now thinks is an Object) as a normal value type.

And here we are. I like that you are treating On Error Resume Next / On Error GoTo 0 as if it were a code block - that's pure awesomeness, because it makes it very clear what the "scope" of "resume next" is.

However On Error Resume Next / On Error GoTo 0 should only be used when you're expecting an error that you're ready to ignore. That isn't what's going on here: you are storing the error number in errNumber, to handle it later. That's poor error handling.

Err.Number is already a Long: the type conversion CLng is redundant, and can be safely removed.

Actually I would remove errNumber altogether. But I'll get back to that in a minute.

 '5 is not in, 0 and 438 represent incollection
 If errNumber = 5 Then ' it is 5 if not in collection
 InCollection = False
 Else
 InCollection = True
 End If

This If...Else block clearly belongs in an error-handling subroutine*. You're handling error number 5 "Invalid procedure call or argument", and letting error 438 "Object doesn't support this property or method" mean yes, that key is in the collection. I think it's wrong.

Your function should only ever return True when it's absolutely 100% certain that the specified key was found.

You will encounter runtime error 450 "Wrong number of arguments or invalid property assignment" if the collection item with the specified key is actually an object reference - your code will correctly return True in that case, but in a somewhat awkward way.

In reality, the only error you should have to deal with is 5 "Invalid procedure call or argument" which means that the specified key isn't referring to anything in the collection. Any other error is due to code that isn't doing exactly what you're expecting it to be doing - and returning True when that happens is plain wrong.


So, how should it be handled then? First, make the very first executable line of code in the function be an On Error statement that will redirect execution flow to an error-handling subroutine:

Private Function IsInCollection(ByVal col As Collection, ByVal key As String) As Boolean
 Dim result As Boolean
 On Error GoTo CleanFail
 'function logic goes here
 CleanExit:
 IsInCollection = result
 Exit Function
 CleanFail:
 result = False
 Resume CleanExit
End Function

The result variable is indeed superfluous, but it allows for a single spot to assign the function's return value. The CleanExit subroutine is responsible for assigning the function's return value, regarldess of the outcome - if the function runs into an error, it's still the exit point and only place where the function's return value is assigned. CleanFail only runs when any error occurs, in which case it explicitly sets the result to False. Simple, straightforward, foul-proof.

So how would the logic be implemented for this to work? We don't really care about the actual collection item - we only care whether it's there or not. Here's how I would do it:

result = TypeName(col(key)) <> vbNullString

Why TypeName? Because I know what type I'm getting: a String, regardless of whether the value is an Object or not. If the key exists, I'm getting the name of the type of the corresponding value. If it doesn't, runtime error 5 is handled in the CleanFail subroutine.

And since I don't care about the actual value, I'm not even allocating it a variable - either I get a non-null string (a null string is vbNullString - literally, it's a null string pointer; "" isn't a null string), or I get an error if and only if the key doesn't refer to an item in the collection.


Well, that ended up a longer answer than I anticipated. Good thing @RubberDuck covered pretty much everything else!


*@RubberDuck is misusing the term "subroutine" in his answer - he means "procedure". A subroutine is identified by a label, lives within a procedure and usually contains a returning mechanism, that the Return or Resume/Resume Next keywords provide. The VBA keywords for jumping to a subroutine are GoTo, and GoSub when you're planning on returning to the call site.

lang-vb

AltStyle によって変換されたページ (->オリジナル) /