I have seen a number of discussions recently showing that some developers can be confused over try/except and try/finally blocks. I can give an example in the kind of work that I was doing this morning.
I often read or write to an external device (let's just call it "the device" as it could be any external device). That device can sometimes send back confusing errors that I try to trap. However because those errors are confusing, I must let the user know all the various issues and ways around them.
Here's a simplified version of my code (the procedures can raise their own exception with the error message returned from the device).
var
curSave : TCursor;
begin
try
curSave := Screen.Cursor;
Screen.Cursor := crHourGlass; // Show hourglass cursor
try
OpenDeviceConnection; // each will raise an exception if error
DoStuffWithDevice;
ObtainReadings;
CalculateNewFigures;
UpdateDevice;
If not CloseConnection then // raise my own error
raise exception.create('Cant Close Connection');
finally
// return the cursor to what it was
Screen.Cursor := curSave;
end;
except
// the original exception is now being handled
// we can re-raise it, but in this case we'll
// raise a new exception with additions
on e:exception do
raise exception.create(e.message + #13+#10
+ 'Please check that the device is switched on' +#13+#10
+ 'and that the cables are properly connected.');
end;
end;
Exceptions created by any of the procedure calls will now result in an error message something like the following...
Note that I have also placed a try/finally block inside the try/except. This is to ensure that we get the cursor back.
I can raise an exception myself with "raise exception.create", the process then heads immediately to the except block, but before doing that, it must complete the finally block.
The order that finally and except will be executed depends on the order that you give it. In the above sequence, finally will be completed before the except. Usually you will see them the other way around where the exception is processed before the finally block.
Hello
ReplyDeleteI think the code could be:
var
curSave: TCursor;
begin
curSave := Screen.Cursor;
Screen.Cursor := crHourGlass;
// Show hourglass cursor
try
DoStuffWithDevice; // will raise an exception if error
If not CloseConnection then // raise my own error
raise exception.create('Cant Close Connection');
finally
Screen.Cursor := curSave; // return the cursor to what it was
end;
end;
Because the
except
Raise; // re-raise the exception
end;
do nothing
Absolutely correct. Of course in real life, there would have been more of a reason.
ReplyDeleteFor example, in the piece that I used, there were several calls like the mythical DoStuffWithDevice, each possibly returning a different error message. Because these error messages can be confusing, or perhaps sometimes even wrong, I actually created a MessageBox type call with the original "e.message" plus some other detail that will allow the user to zero in on the actual fault (e.g. a disconnected cable).
But you are totally correct, in this case there would be no need for the try/except block and it was only placed there to show how they could be used together.
This comment has been removed by a blog administrator.
ReplyDeleteGidday everyone,
ReplyDeleteWell, that will teach me to quickly fire off a posting without reading it and then leave for the weekend. I have only just read my own posting for that day.
As you will have gathered from my last comment, code came from a real life example. However, that code belongs to the company I work for and I would not post their property on my blog. I quickly set up a very trimmed down example, too trimmed down it seemed.
I will change the post to give a proper example. Apologies to all.
To Anonymous - You are most welcome to post again but please limit your criticism to a tone that is not derogatory.
No no and THRICE NO!!
ReplyDeletetry
:
except
raise Exception.Create(..);
end;
is the spawn of the DEVIL himself!!! Doing this hides crucial information about the original exception, most notably the address at which it occured, but also changes the class of the exception to bog-standard Exception which might be a CRUCIAL ERROR as far as any outer exception handlers are concerned.
(when I say "changes the class, I mean the effect, not the act. The ACT is to handle/suppress/destroy the original exception and raise a whole new one)
Sometimes this is legitimate, but if all you are doing is adding information to an exception message then you should:
try
:
except
on e: Exception do
begin
e.Message := e.Message + #13#13
+ 'Some additional useful information about the exception.';
raise;
end;
end;
Thanks for your observation Jolyon.
ReplyDeleteWhile you may be correct in your statement (I certainly won't argue with your assertion on the change in the original exception), this was a simple situation.
My own code actually creates a MessageDlg, which most definately handles and removes the original exception, e.g.
MessageDlg(e.message + 'Receiving an error at this point may be a result of the cable becoming dislodged etc Blah Blah', mtError, [mbOK], 0);
Raising an exception in this case would create a similar effect to simply showing a MessageBox. In either case, the user (off site non-computer literate) may be informed of even a critical error so the real effect to the use of the application would be the same.
Please remember, this is a simple example of using both except and finally in the same block, not of the technicalities of the exception itself.
You do however point out a very valid point, perhaps for another discussion sometime.
Oh, and neither Satin himself, nor any of his spawn belong in my office :-)
Thanks again.
Hi Steve - I get that this is supposed to be a simple example, but the example itself says that it is re-raising the exception when it isn't: It's destroying the exception and raising a whole new one.
ReplyDeleteSimple examples are only simple if they don't raise new [sic] questions.
;)
Off topic: Are you going to be at the Crowne Plaza tomorrow (Thursday?)
Aargh! thanks Jolyn, that was meant to come out in the change I did. It originally re-raised the exception, but I changed it. I'll update the post now.
ReplyDeleteSorry, sadly I won't be there tomorrow. Another time.
Steve
As a relatively late returner to Delphi I'm annoyed you can't just do
ReplyDeleteTry
stuff
Except
Handle Bad things
Finally
Tidy up.
essentially in that order, you always seem to have to have the internal "try" bit, is that correct?
Apologies, as this bugs me coming from C#
Chris,
ReplyDeleteOf course you can. the try/except and try/finally blocks are independent of each other. Your suggestion would look like this...
Try
Try
stuff
Except
Handle Bad things
end
Finally
Tidy up
end
On the exception, errors are handled, then passes to the finally to tidy up.
Steve
Chris, I think I know what you are getting at now. You expect a single try for both except and finally.
ReplyDeletethis is where the Delphi sentance comes in. They are both seperate sentences and therefore treated as seperate. It is the underlying structure of Pascal that allows for the different statements of try/except and try/finally.
Good to see a returning programmer coming back into the world of delphi. I'm sure you will enjoy it.
Steve
Cheers steve, your second example clears it up for me, its added to my snippets" list (rapidly growing).
ReplyDelete