Skip to main content
5 of 5
replaced http://stackoverflow.com/ with https://stackoverflow.com/

There's no such thing as bullet proof code. It's a mythical legend, a unicorn. We can (and should!) certainly try though. The first step to doing that is writing extremely legible and understandable code. You've done a pretty good job of that, but there's some room for improvement.

#Readability ##Use of Whitespace and Comments

It seems like a silly thing to pick on, but it really does matter. Consider this snippet from your code. It does some things right, but it looks messy.

For g = 1 To 10000
    If Not IsEmpty(Cells(4 + g, 4).value) Then
        NumberOfTags = NumberOfTags + 1
    End If
Next g

If Not ThisWorkbook.OPCServer1 Is Nothing Then       'safety
     Exit Sub

End If

Set ThisWorkbook.OPCServer1 = New OPCServer
Call ThisWorkbook.OPCServer1.Connect(Cells(4, 2))                'connect to the OPC Server
Set OPCGroup1 = ThisWorkbook.OPCServer1.OPCGroups.Add(GrpName) 'add the group

'add the 6 items
ReDim MyOPCItems(NumberOfTags)


For i = 1 To NumberOfTags
    On Error GoTo Error_TagNotFound
    If Cells(4 + i, 6) = 1 Then

The indentation is good, but you seem to arbitrarily use new lines. Sometimes no empty line, sometimes one, sometimes two. It makes for hard to read code. Hard to read code is hard to maintain code. Hard to maintain code will have bugs.

The other thing going on here is the "right aligned" comments. Which, as you can see, aren't right aligned anymore the second you copy paste them anywhere else. I'm okay with end of line comments if they're short and helpful, but forget trying to align them. It's a waste of time. If they state the obvious, remove them. If they are more than a word or two, move them to the line before.

For g = 1 To 10000
    If Not IsEmpty(Cells(4 + g, 4).value) Then
        NumberOfTags = NumberOfTags + 1
    End If
Next g

If Not ThisWorkbook.OPCServer1 Is Nothing Then
    Exit Sub
End If

'connect to the OPC Server
Set ThisWorkbook.OPCServer1 = New OPCServer
Call ThisWorkbook.OPCServer1.Connect(Cells(4, 2))   

Set OPCGroup1 = ThisWorkbook.OPCServer1.OPCGroups.Add(GrpName) 

'add the 6 items
ReDim MyOPCItems(NumberOfTags)

For i = 1 To NumberOfTags
    On Error GoTo Error_TagNotFound

##Scoping (kind of)

This is not a scoping issue with your code per se, but still a readability issue.

'Dim OPCServer1 As OPCServer
Dim WithEvents OPCGroup1 As OPCGroup
Dim MyOPCItems() As OPCItem
Dim NumberOfTags As Integer
Dim ReadInterval As Double

Dont' use Dim at the module level. Use Private. Yes, they both do the same thing, but using Private is a visual indication that these variables belong to the module scope.

You should also explicitly declare the scope of your Subs & Functions. Everything is public by default, but I find it much nicer to say so. (Particularly since in VB.Net, everything is private by default. It makes for less confusion when switching between the languages.)

So,

Sub OPC_Connect()

Should be

Public Sub OPC_Connect()

Or

Private Sub OPC_Connect()

Probably the latter. As a rule of thumb, declare everything private until you prove a need to call it from outside the current module or class.

Dim i As Integer
Dim g As Integer
Dim h As Integer

Here you're declaring 3 different counter variables and using them once. Don't do this. It's clutter. Just re-use the i variable as a counter for each loop. The eye will instantly recognize it as a loop counter. Or even better, extract some methods so you don't have to stress about re-using variables. (I'll come back to that in a bit.)

That was also more of a readability issue, but this "scoping" issue is a seriously dangerous thing. I can not think of a single situation where I ever should have legitimately called just plain old Cells.

'fetch the values from the cells
For i = 1 To 6
 Values(i) = Cells(8 + i, 3)
 If Values(i) = "" Then
  Values(i) = 0
 End If
Next i

Cells the way you have used it here is equivalent to calling ActiveSheet.Cells. Almost never should we be working with the active sheet because the user can change it on us while the code is executing. Avoid activate and select by explicitly setting a variable to the range you want to work with.

The same goes for Sheets. This code

If Sheets("Setup").audible_alarms_enable_ChkBx.value = True Then

Is equivalent to calling ThisWorkbook.Sheets("Setup"). If the user clicks on another workbook, your code is going to blow up because it can't find the "Setup" worksheet.

##Naming

Names should not have underscores in them unless they are an event procedure. The underscore holds this special meaning in VBA. If it's not an event procedure, just don't do it. It's confusing. Particularly when you're making use of WithEvents. (Kudos for that by the way. I don't see many programmers take advantage of that.)

##Remove Dead Code

Dead code is clutter. There is dead commented out code all over the place. Burn it with fire. If you're worried about needing that code again, I highly recommend that you start using source control. I wrote a blog about using Git with VBA that should get you started.

#Single Responsiblity

You've done an okay job of using subroutines, but you could do better. Remember those alphabetic counters? Extract them into well named subroutines and call them from your "main" routine.

Private Sub NotSureWhatThisActuallyDoes()
    For h = 1 To 7
        Module1.SavedThisTime(h) = False
    Next h
End Sub

A corner stone of "bullet proof" code is that you can easily grok an entire routine at one time. It's often referred to as the single responsibility principle. Each routine or function should have one and only one job. Another way of thinking about it is the "Single Screen Principle". I should never have to scroll to see more of a routine. If I have to scroll, then the routine is most certainly breaking the SRP.

Sidenote: Speaking of "calling" routines, don't actually use the Call keyword. It's archaic and only available for back ward compatibility reasons.

Here is a really good example of an opportunity to extract a method. The comment is a dead give away (and a good start on a good name).

'set up which items to be read
For i = 1 To NumberOfTags
    ServerHandles(i) = MyOPCItems(i).ServerHandle
Next i

#Other things

   'free the memory
   Erase Values()
   Erase Errors()

Exit Sub

It's nice that you're cleaning up after yourself, but I seriously don't understand why you're doing this. These arrays are locally scoped. Once they go out of scope the reference count will decrement to zero and the memory will be reclaimed.

RubberDuck
  • 30.9k
  • 6
  • 71
  • 174