One of the key concepts that we all need to get our minds around when we first start programming in Delphi is the idea of Exceptions and how to handle them. At the same time, we commonly learn about resource protection and how to ensure our resources are freed.
I thought I had a pretty good handle on the way these things worked - until they turned around and started to bite. When I started delving into things more deeply, I found that many of the habits I had weren’t as good as I thought. In fact, some of my habits were actually reducing the reliability of my code, my components, not increasing it.
Consider the following code fragment:
How many problems can you see with it?
Looks like a fairly standard bit of code - and in most situations, it will work, exactly as intended. I must confess, I have been guilty of writing code like this.
There are two very significant problems with this code.
The try..finally block is intended to ensure the Generator is properly cleaned up and freed after it is used. Problem is, if the constructor of the Generator object raises an exception, the finally block is still run - and the Cleanup call will certainly crash. Causing an exception while handling an exception tends to cause very nasty things to your application - such as quiting it on the spot.
Similarly, the try..except block is intended to catch the creation of an invalid object by the Generator. Unfortunately, the except block will always be run, even if the problem is with retrieving the object in the first place. Worse, every kind of exception is being caught - from EMathError
, EOutOfMemory
and EOleCtrlError
to EOutlineError
and EPrinter
, this exception handler takes the lot.
Here is the same routine, rewritten to be safer and more reliable:
Note the differences in the second example.
-
The constructor of the Generator is outside the try .. finally block If something goes wrong in the constructor, the Cleanup method is not called, and the exception is cleanly passed to the caller of this routine. If the Generator is cleanly constructed, the finally block ensures that it is cleaned up again afterwards.
-
The object is extracted from the Generator outside the try..except block. An error in extracting the object will raise an exception which breaks out of the routine (cleaning up the generator on the way). If a problem is found with the object, it is handled cleanly and the loop continues. Any other kind of error (say, an
EOverflow
math error from theProcessObject
method) aborts the procedure.
The key point to note is that the method doesn’t continue running when something has gone wrong. When the routine finishes cleanly, you can rest assured that it has run correctly and nothing has gone wrong.
Here are a couple of tips to keep in mind:
Firstly, use try..finally blocks to ensure that resources are freed - but put the allocation of those resources outside the block so that they are not cleaned up unless you get them in the first place.
I use a construct like this throughout most of my code:
Secondly, Use try..except blocks to catch exceptional occurences in your code - but only catch the kinds of exceptions you can handle.
If you ever find yourself writing code like this, reconsider:
Be very suspicious of code like this - can you really handle a TOutofMemory
and a TBDEError
with the same handler?
Comments
blog comments powered by Disqus