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:

procedure Example1;
var
  oGenerator:   TObjectGenerator;
  iScan:        integer;
begin
  try
    oGenerator := TObjectGenerator.Create(0,100);
    for iScan := 0 to oGenerator.Count-1 do
      try
        ProcessObject( oGenerator.Item[iScan]);
      except
        on E:Exception do
          AlertInvalidObject( oGenerator.Item[iScan]);
      end;
    AlertSuccessful;
  finally
    oGenerator.Cleanup;
    oGenerator.Free;
  end;
end;

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:

procedure Example12
var
  oGenerator:   TObjectGenerator;
  iScan:        integer;
  oObject:      TObject;
begin
  oGenerator := TObjectGenerator.Create(0,100);
  try
    for iScan := 0 to oGenerator.Count-1 do begin
      oObject := oGenerator.Item[iScan];
      try
        ProcessObject( oObject);
      except
        on E:EInvalidGeneratorObject do
          AlertInvalidObject( oObject);
      end;
    end;
    AlertSuccessful;
  finally
    oGenerator.Cleanup;
    oGenerator.Free;
  end;
end;

Note the differences in the second example.

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

  2. 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 the ProcessObject 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:

oSomething := TSomething.Create;
try
  ...
finally
  oSomething.Free;
end;

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:

try
  ...
except
  on E:Exception do
    ...
end;

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
Next Post
Streams in .NET  20 Nov 2003
Related Posts
Using Constructors  27 Feb 2023
An Inconvenient API  18 Feb 2023
Method Archetypes  11 Sep 2022
A bash puzzle, solved  02 Jul 2022
A bash puzzle  25 Jun 2022
Improve your troubleshooting by aggregating errors  11 Jun 2022
Improve your troubleshooting by wrapping errors  28 May 2022
Keep your promises  14 May 2022
When are you done?  18 Apr 2022
Fixing GitHub Authentication  28 Nov 2021
Archives
December 1998
1998