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.
#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.