Note that the whole sub is wrapped within conditional compilation, to make it possible for you to decide if you like the assertions to be checked during execution at the customer site or not. (I’m aware of a repeating problem in my articles and that is that I’m constantly referring to upcoming articles. Sorry about that, but I must again commit that mistake and state that I will use an upcoming article to show how to make it configurable at runtime if the assertions should be used or not.)
Note also that I have even prefixed my conditional compilation argument. I’ve had problems when merging my code with others in components and used the same name for those arguments. It’s not a big or common problem I guess, but better safe than sorry.
As you saw above, I wanted to know if the code had been executed from the IDE or in an executable. Here is a way to achieve that:
#If jnskDebug Then
Public Function IsIDE() As Boolean
IsIDE = GetModuleHandle("VBA6.DLL") <> 0
(And as always in my articles, I skip comments in the header and such. I don’t do that in the real case!)
Even if we set the conditional compilation argument so that the assert code isn’t compiled into the executable (
jnskDebug = 0), there will be a slight overhead since an empty sub will be called. I have written a utility like a preprocessor that works with a copy of the source code and can help with this problem. You can easily write a similar one on your own. You could also wrap all the calls to the sub with conditional compilation, but then the code would be filled with several more statements...
Note that the assertions (such as
jnskAssert) aren’t used instead of
Err.Raise assertions, it’s something else. An assert-call should always be possible to remove from the code. Assert is only intended to proactively detect bugs, not to handle "expected" errors and such.
Debug.Assert you can’t do very much about how the broken expectation is signaled to the developer. You just get a break on the code line with the problematic
Debug.Assert. In my proposal you can decide what should happen. You could for example do approximately the same thing as
Debug.Assert when you’re in the IDE. When you’re running the deployed component, you can send the information as a trace message, write to the application log, and so on, as you saw was intended in the example above.
In Bertrand Meyer’s DBC, there is a concept called class invariants. If we recall the example with the snail-mail earlier, there isn’t anything about social contracts or laws that says we’re not allowed to send narcotic drugs and bombs for example by snail-mail. If someone does try to do that, the contract is not valid and he can’t expect to have the service executed. (Hopefully the police will provide another service for him instead.) All contracts would be extremely long if we had to repeat those things repeatedly. The same goes for classes in software. If a class for a person has a member variable called
mintAge for example, the variable may never have the value of less than 0 and never of more than 125. This condition shouldn’t have to be expressed repeatedly in all the methods of the class. Still, it would be very good to have it checked in every
Ensure section, to help discover a nasty bug.
How do we arrange for that? A possible solution would be to have a sub like the following in each class:
Private Sub InvariantsCheck _
(ByVal vstrSource As String, _
ByVal vobjCtx As ObjectContext)
#If jnskDebug Then
jnskAssert Len(mstrName) > 0, _
"Len(mstrName) > 0", _
'More assertions to check.
Then, that sub should be called from the
Ensure sections in all the
Public methods. The call can be added by a utility to a copy of the code, or you could add it manually of course.
As you saw before, you can of course create complete functions that return
False to provide more advanced logical expressions. Don’t forget to wrap those within the
jnskDebug argument as well.
There is much more to DBC, for example when it comes to implementation inheritance. I will save that discussion until we have VB.NET.
When you run your automatic testing for your components (you do that, right?), you will automatically catch a bug or two just because of the signals from the assertions. Together with comparing the result of the test with the expected result, your assertions are making the net (no, for once net doesn’t mean .NET) finer-meshed.
A second bonus is that I believe the contracts to be great documentation. I’ve seen its use even without the automatic and built-in testing that I’ve discussed here, just because it expresses much of the semantics of the classes and its methods. You can easily build a utility that drags out, for example, the method signatures together with the assertions to create one important part of the documentation of your application.
This documentation is very valuable for public components. The same goes if you work together with someone else in a project. Those contracts express responsibilities in a very natural way. The assertions may also be a handy instrument at design time. They help you to be detailed about the responsibilities of the methods, without writing all the code.
I’ve already talked about assertions when it’s time for a new version of a component, but I’d like to do it again. (No, I’m not being paid by word here. I’m just tedious.) You will be especially fond of your assertions in such a situation. (Have you ever introduced a new bug in the old code when you created a new version? Don’t lie to me!) Most of the assertions should still be valid and you will see at once if you introduce bugs that break the assertions. Very good for productivity and quality!
WHAT ABOUT COM+/MTS?
The title of the article insinuates that it would be especially targeted at COM+, right? You will send the
jnskAssert and by doing so several things can be outputted that help understand why an assertion was broken, such as by whom the component was called.
I would also like to give a few small examples that could be useful:
jnskAssert mobjCtx.IsInTransaction, _
jnskAssert mobjCtx.IsSecurityEnabled, _
Let’s say your customer calls you about a bug in your application that has led to inconsistency in the database. You ask him to send over a trace-log and there you see that a specific component is breaking an assertion since it isn’t using COM+ transactions. When you dig further into it, the customer tells you that the sysadm changed that attribute for the component. His sysadm thought that was good. You can then decide if it’s a bug in your deployment documentation or with the sysadm at your customer site...
Well, the commonest one is that your assert checks change the execution in any way. Watch out!!! A typical example is that the assert code does some moving around in a recordset, or something like that. Make sure you use a clone in that case.
No matter what, there will be some minor differences when you use assertions and when you don’t since the code isn’t exactly the same. Just take care so you’re not affected. John Robbins discusses this and more in his excellent book "Debugging Applications."
Should the assertions be left in the compiled code so that they execute at runtime? Well, there is a cost in overhead of course. On the other hand, most often the cost is quite small and it’s great to have the system tell you about when it’s not feeling good and in fact even what is wrong sometimes. Let’s face it. Most development projects are running in Internet time, and there isn’t six months of testing before the components are rolled out.
I leave my assertions in the executable for a couple of weeks after deployment time and after a change. I use a configuration trick so that I can turn off the assert checks during runtime, and almost take away the entire overhead associated with the assertions. If there is need for even better performance, I re-compile without the
jnskDebug flag to get a slimmed version later on, and the calls can also be removed from a copy of the source code by a small utility of mine.
What about ASP and stored procedures in SQL Server? Well, you can use the same basic concept there if you like to. (And you do, right?) I will demonstrate this in an upcoming article.
Assertions are not a way of skipping other tests. It’s only yet another complement. It’s time for my favorite expression: "There is no silver bullet!"
If you haven’t used assertions before, I hope I have motivated you to try it. If you have used assertions, perhaps you will now go on and take it to the next level.
OK, for several articles now I’ve mentioned tracing. I guess it’s time to discuss some about that in the next article. I have planned to deliver it in three parts. The second part will carry a lot of code, the very same code that I have promised to show you in several articles. Any month now...