Let’s take a look at the code flow, when everything works out as expected.
'Do some work...
On Error Resume Next
'Clean up, for example ADO-stuff.
If lngErr = 0 Then
jnskSetCompleteMTS strSource, mobjCtx
As you "know", we will get
Finally semantics in VB.NET, but until then I try to simulate the same stuff with VB’s old error handling scheme. (I know that I’m not achieving that fully when it comes to clear syntax, but the functionality is quite close.) The
ExitHandler label will always be used at the end of the methods flow. Therefore you have just one place to put your clean-up code. It’s good practice to clean up after yourself. It’s also a good way of avoiding strange problems. A classical example has been (is?) that if ADO-objects weren’t closed explicitly, they would empty the error object, when VB closed the ADO-objects implicitly. Before I start cleaning up, I say that I don’t care about new errors.
After cleaning up, I take a look at the local error variable
lngErr. If it’s zero, everything is OK, and it’s time for
SetComplete. How can
lngErr not be zero you ask? Well, be patient and you will soon find the answer.
Once again, I have my own sub for accomplishing a task. This time it’s the
SetComplete that will be taken care of by
jnskSetCompleteMTS. As you might have guessed, I have several arguments for doing this, such as:
- It gives a nice and central place for adding "custom interception", such as trace calls. (I’m sorry for nagging, but this will be covered further in another article.)
- It moves the need for conditional compilation sections out of the regular code.
- It’s a preparation for a possible need in the future.
The following code will run when the code reached an error.
jnskSetAbortMTS strSource, mobjCtx
On Error Goto 0
Select Case lngErr
jnskErrRaise doErrUnexpected, lngErr, _
strError, lngErl, strSource, mobjCtx
lngErr is not zero, we enter the section where we will investigate the problem further. Before doing that I will do a
SetAbort. It could be argued whether this is a good idea because perhaps we can do something to recover from the problem higher up in the call hierarchy. I agree that
SetAbort isn’t what should be used all the time, but most often I do it like this instead of using
DisableCommit. "Simple is beautiful".
Error trapping is then deactivated so that we will pop out of this method when we raise a new error. After that the error is investigated. If we didn’t expect this error, a last resort would be to raise an unexpected error.
jnskErrRaise takes an enum for which error was raised (in this example the sample component lives in the layer I call Domain and that’s the reason for using "do" as the prefix to the new error code). I also send all other relevant information to the
jnskErrRaise sub so it can do an
Err.Raise and some other interesting stuff like logging the error etc.
jnskErrSave lngErr, strError, lngErl
When an error occurs in the regular code, I will go to the
ErrorHandler above. The first thing I do there is to call a sub (
jnskErrSave) that will save the error information in my local variables. That way you don’t risk the problematic effect of having the error object emptied. (The error object is still valid in
jnskErrSave since I don’t set up any error trapping in that sub.) Note that I will save the
Erl information to the local variable called
lngErl. This means that if you have numbered the lines of the code, you have caught information about the exact line where the error happened. That is really powerful, when it comes to trying to find an error.
After that I call
jnskStop which is only a sub, acting as a persistent breakpoint. I often find it interesting to get a halt in my code as close to the error as possible. If I have places in my code where I expect errors to happen, I don’t have the problem as with the regular "Break in Class Module" that can be set in the VB IDE, since using
jnskStop is configurable.
Finally I jump to the
ExitHandler (as you saw earlier) for further processing of the problem.
WAS THAT ALL THERE IS TO IT?
No, of course not! This is only the beginning. I was lazy and trapped only the unexpected case. I didn’t really do anything with the problem in this example. I only popped it. Some errors should of course be taken care of locally.
You also have to add more standard code, for example when it comes to trapping errors from ADO and the database. I haven’t shown many comments either, not even headers for the class and the method. You should of course add a lot of those. And more...
Do you think that there is more code here for error trapping than "real" code? This is often the case. But remember the parallel with re-papering. If you want to have a good end result, then the preparation will often take more time than the "real" work. You could of course also easily create an add-in for writing the skeleton for you.
Do you like to use the same code structure for components that shouldn’t run as configured components in COM+/MTS? I certainly do because of several reasons, but I will skip those arguments for now. To accomplish that, I’m using a trick by faking an object context interface, an object control interface and a conditional compilation argument that I have called MTS. (The faked interfaces are there only to make the code compilable. I could escape those faked interfaces by adding a lot more conditional compilation sections to my code, but I think this strategy gives much cleaner code since I don’t have any conditional compilation sections in the "real" code, only in my shared foundation routines.) There are of course other strategies that could be used too. You could for example check if
GetObjectContext() is returning
Nothing. You can also see if
ObjectControl_Activate was executed. This is actually yet another subject that needs an article of its own.
I also write my transactional code so that if your transactional component isn’t configured for COM+ transactions, it will use ADO transactions instead. Boy, do I have a lot of articles that I need to write or what?
Is it really important nowadays to pay attention to the code structure, the obscure error trapping stuff in VB6, and so on, when VB.NET is soon to arrive? Well, in my opinion the answer is "yes". (You would have been surprised if I said "no" here, right?) First, VB.NET won’t be here soon and perhaps you would like to do some work now? I also believe that the best preparation for the future is to have a solid ground with consistent code, error trapping construction that maps the one in VB.NET and as much as possible in general and shared routines. All this should give as smooth a transition as possible when it’s time.
Are there any drawbacks with standardised code? Some developers argue that it will lead to a situation where developers stop thinking and don’t understand what’s going on. On the other hand, the code will be consistent and probably easier to understand. Of course, you must always understand the code, otherwise you can’t use it. It will blow up in your face one day.
I don’t use GlobalMultiUse classes for my jnsk-subs/functions. Instead I use plain old code modules. The reason for this is that I’ve had strange problems with GlobalMultiUse classes in configured components and when you have had a problem, you don’t forget it for a long time, right? The problem might have been solved ages ago, but better safe than sorry. (I’m not the only one to having had those problems, by the way. There are every now and then discussions about this in the COM+ newsgroups.)
There was an often-discussed problem in COM+ Component Services version 1, where the error object was emptied when the root object called a second object, the second object and root object both called
SetAbort. This problem is solved with Windows 2000 SP1 (and it wasn’t there in the first place in MTS). If you like to use this code structure in COM+ before the SP1, then you have to skip the
SetAbort in the secondary object. But do then remember that you shouldn’t use that object (at least not the same method), as a primary object.
In my latest article at VB2TheMax An error that must be trapped I said that if you disable JITA, you won’t get
Deactivate for the
ObjectControl-interface. This can be a reason for using
GetObjectContext() everywhere instead of keeping a module-global
mobjCtx as I do in my proposal above.
As usual with my articles, most of the stuff is also relevant if you use NT4 and MTS.
In this article I have shown you my ordinary code structure for COM+ components. Hopefully there was an idea or two for you to adopt. Next time I will discuss Assertions and hopefully give you a few new tips on how to use it, we’ll see.
Finally, perhaps you are wondering how the re-papering of the room went? Well, it’s finished any month now.