In delphi 7, is `try ... except raise; end;` meaningful at all? - delphi

In some Delphi 7 code I am maintaining, I've noticed a lot of instances of the following:
with ADOQuery1 do begin
// .. fill out sql.text, etc
try
execSQL;
except
raise;
end;
end;
It seems to me that these try blocks could be removed, since they do nothing. However, I am wary of possible subtle side-effects..
Can anyone think of any instances in which these blocks could actually do anything that wouldn't happen without them there?

In this context, the raise operation has no effect and should be removed becuase its simply re-raising the exception that the exception block just caught. raise is typically used to transfer control to the end of the block when no appropriate error handling is available. In the following we handle the custom exception, but any other exception should be handled elsewhere.
try
someOperation;
except
on e: ECustomException do
SomeCustomHandelr;
else
begin
// the raise is only useful to rethrow the exception to an encompasing
// handler. In this case after I have called my logger code. as Rob
// mentioned this can be omitted if you arent handling anything because
// the compiler will simply jump you to the next block if there is no
// else.
LogUnexpectedException('some operation failed',e);
raise;
end;
end;
Be careful that there is similar looking form without the "raise" that DOES have the side effect of eating/hiding any exceptions. practicies by very unscrupulous developers who have hopefully moved on to positions with the competition.
with ADOQuery1 do begin
// .. fill out sql.text, etc
try
execSQL;
except
// no handler so this just eats any "errors"
end;

Removing the except code in above code snippet will make no difference. You can (and I believe you should since it is reducing the readability) remove it.

Okay, really two questions here.
First, it is meaningful: if execSQL throws an exception, it's caught by the try block and forwarded to the except. Then it's forwarded on by the raise to the next higher block.
Second, is it useful? Probably not. It almost certainly is the result of one of three things:
Someone with pointy hair wrote a coding standard that said "all operations that can throw an exception must be in a try block."
Someone meant to come back and turn the exceptions made by the execSQL statment into some other, more meaningful, exception.
Someone new wasn't aware that what they'd written was isomorphic to letting the uter environment worry about the exception, and so thought they must forward it.

I may have answered a bit fast, see at the end...
Like it is, it is useless for the application.
Period!
Now on the "why" side. It may be to standardize the exceptions handling if there /was/will be/is in other places/ some kind of logging code inserted before the raise:
try
execSQL;
except
// Log Exception..
on E: Exception do
begin
LogTrace(Format('%s: Exception Message[%s]',[methodname, E.Message]));
raise;
end;
end;
or for Cleanup code:
try
execSQL;
except
//some FreeAndNil..
raise;
end;
Update: There would be 1 case where I would see some use just like it is...
... to be able to put a Breakpoint on the raise line, to get a chance to see what's going on in the context on that block of code.

This code does nothing, other than to allow the original programmer to place a breakpoint on the 'Raise' and to see the exception closer in the source to its possible cause. In that sense it a perfectly reasonable debugging technique.

Actually, I should posted this as comment to François's answers, but I don't know is it possible to insert formatted code there :( So I'm posting this as answer.
2mghie:
The second one is completely unidiomatic, one would use finally instead.
No, "finally" will cleanup object always. "Except" - only on exception. Consider the case of function, which creates, fills and return an object:
function CreateObj: TSomeObj;
begin
Result := TSomeObj.Create;
try
... // do something with Result: load data, fill props, etc.
except
FreeAndNil(Result); // oops: bad things happened. Free object to avoid leak.
raise;
end;
end;
If you put "finally" there - function will return nil always. If you omit "try" block at all - there will be resources leak in case of exception in "...".
P.S. Of course, you can use "finally" and check ExceptObj, but... isn't that ugly?

The title contains quite a broad question, while its explanation gives a more specific example. So, my answering to the question as how it proceeds from the example, can doubtly add anything useful to what has already been said here.
But, maybe Blorgbeard indeed wants to know whether it is at all meaningful to try ... except raise; end. In Delphi 7, if I recollect correctly, Exit would trigger the finally part of a try-finally block (as if it were some sort of exception). Someone might consider such behaviour inappropriate for their task, and using the construction in question is quite a workaround.
Only it would still be strange to use a single raise; there, but then we should have talked about usefulness rather than meaningfulness, as Charlie has neatly observed.

This code does nothing except re-raising an exception that will allready be raised without this try except block. You can safely remove it.

Related

how do i create Tstrings with onchange event?

i know how to make Tstringlist onchange event , but what about Tstrings ? i dont want to use VCL like tmemo or something . can i do that ? is it possible to have this event on tstrings and do something when its changed ?
i tried to do something like this but got access violation
//on form show event
stringlist:= TStringList.Create;
stringlist.OnChange := HandleStringListChange;
//implantation
procedure TChatFo.HandleStringListChange(Sender: tObject);
begin
if stringlist.Text <> '' then
ProcessCommands(stringlist.Text);
stringlist.Clear;
end;
exception messages
Project Project1.exe raised exception class $C0000005 with message
'access violation at 0x6d40c92c: read of address 0x00000150'.
Project Project1.exe raised exception class EStringListError with
message 'List index out of bounds (5)'.
Project Project1.exe raised exception class EStringListError with
message 'List index out of bounds (5)'.
this tstringlist should work as command identifier i creat it with my thread
as example
type
TReadingThread = class(TThread)
protected
FConnection : TIdTCPConnection;
FLogResult : TStrings;
procedure Execute; override;
public
constructor Create(AConn: TIdTCPConnection; ALogResult: TStrings); reintroduce;
end;
ListeningThread := TReadingThread.Create( TCPClient, stringlist);
constructor TReadingThread.Create(AConn: TIdTCPConnection; ALogResult: TStrings);
begin
FConnection := AConn;
FLogResult := ALogResult;
inherited Create(False);
end;
procedure TReadingThread.Execute;
Var
strData : String;
begin
while not Terminated do
begin
try
strData := FConnection.IOHandler.ReadLn;
if strData <> '' then
begin
FLogResult.Add( strData );
end;
except
on E: Exception do
begin
FConnection.Disconnect(False);
if FConnection.IOHandler <> nil
then FConnection.IOHandler.InputBuffer.Clear;
Break;
end;
end;
Sleep(10);
end; // While
end;
if i use Tmemo no errors or exception happened.
We're all shooting in the dark here because you haven't provided all the relevant code. That said, the information you have provided has a lot of problems, and I can offer advice to help you solve it.
Debugging 101
Run your code through the IDE. When you get your exception, the debugger stops at the line that caused the exception.
This is usually the most important step in figuring out what went wrong. You have access to this information. Run your program and carefully look at the line that raised the error. You might be able to already figure what caused the error. If not, a other basic techniques can be applied to get more information:
Get values of objects and variables on the error line and other close lines. You can hover your mouse cursor to get tool-tips, or press Ctrl + F7.
You can examine the call stack of lines leading to the one that caused the error by double-clicking the previous line in the call-stack.
Put a breakpoint on the line before the error and re-run the app. The debugger will stop on that line and give you a chance to check values as explained earlier, but before the error happens.
Asking for help 101
Getting help from people is much more effective when you give them all the relevant information. For a start, the line of code where the access violation occurs would be extremely useful.... Tell us!
Give us real code.
Saying "I tried to do something like this" is not particularly useful. Please copy and paste exactly what you tried. If your code is different, your mistake might no longer be there.
Access Violations
You get an access violation in the following situations:
You forgot to create the object you want to use or didn't assign it to the appropriate variable.
You created the object but Destroyed or Freed it already before trying to use it again.
You changed the variable that was referencing the object.
You performed a 'hard-cast' (or unchecked typecast) from one type to an incompatible type.
The above are the basics. There some variations, and a few special edge cases, but these account for the vast majority of mistakes.
So using the above, that's what you need to check. If you had copy-pasted more of your code, we might be able to see what you did wrong.
NOTE: One shot-in-the-dark possibility is that you are destroying your string list in the wrong place. And perhaps the memo works because as a component dropped on the form, you're not trying to destroy it.
Stack overflow
Let's examine what happens in your OnChange event when for example a string is added:
The event fires.
Text is not empty.
So you call ProcessCommands
You then call Clear
The the end of Clear, Changed is called again.
Which fires your event again.
This time Text is empty, so you won't call ProcessCommands
But you do try to Clear the string list again...
This could go on forever; well at least until the call-stack runs out of space and you get a stack-overflow error.
Saved by the bell
The only reason you don't get a stack overflow is because Clear doesn't do anything if the string list is empty:
procedure TStringList.Clear;
begin
if FCount <> 0 then //You're lucky these 2 lines stop your stack-overflow
begin
...
FCount := 0; //You're lucky these 2 lines stop your stack-overflow
SetCapacity(0);
Changed;
end;
end;
I suggest you rethink how to solve you problem because code that leads to unintentional recursion is going to make your life difficult.
Working with threads
You really need to get a handle on the basics of programming before trying to work with threads. Multi-threaded programming throws in a huge amount of complexity.
I can already see a huge possibility of one potential mistake. (Though it depends what you're doing inside ProcessCommands.)
You modify your string list in the context of a thread.
This means your OnChange event handler also fires in the context of the thread. (The fact it's implemented on the form is irrelevant.)
If your ProcessCommands method does anything that requires it to operate on the main thread, you're going to encounter problems.
As a final consideration, I've noted quite a few beginner programmers completely miss the point that code starting a thread can finish before the thread does. E.g. (Going back to the topic on Access Violations.): If you're destroying your string list soon after creating your thread, your thread could suddenly throw an access violation when the object it had earlier is suddenly gone.
The likely explanation for your error is that you are modifying the list in its OnChange event handler. That is simply not allowed. An OnChange handler must not mutate the state of the object.
What is happening is that you have some other code, that we cannot see, that modifies the list, perhaps in a loop. As it modifies the list your event handler clears the list and then the calling code has had the rug pulled from underneath it.
Of course, I'm having to guess here since you did not show complete code. Perhaps the specifics vary somewhat but it seems likely that this is the root of your problem.
You'll need to find another way to solve your problem. With the information available we cannot suggest how to do that.
Looking at your updated code, there's no need for a string list at all. Remove it completely. Instead, where you do:
FLogResult.Add( strData );
do this:
ProcessCommands( strData );
TStrings is an abstract class, ancestor of TStringList. You can create an instance of TStringList and use it as a TStrings.

Creating or forcing an error in Delphi

For some of my procedures and functions, I have implemented various checks on parameters in order to force execution to stop if parameters are out of range in one way or another.
I find it better to check for this in my own code rather than have an abnormal crash due to a perhaps bad memory-write.
Consider the simple code:
PROCEDURE Test(OneDigitNumbers:BYTE);
BEGIN
IF OneDigitNumbers>9 THEN ProduceErrorMessage;
END;
begin
Test( 1);
Test( 2);
Test( 9);
Test(12);
end.
I have no problem in actually producing an error message, my only "problem" is that the debugger in Delphi always point to the procedure creating the exception.
Is there a method of creating this exception or error message so that the debugger point to the line where the parameter is out of range?
In my example, it should point to :
Test(12);
and maybe say something like "Parameter out of range. Valid range is 0-9. Parameter passed was: 12"
Even an answer to say that this is NOT possible will be useful (if you know for sure that this is not possible), because then I will just forget about this and make an alternative method for debugging.
To answer the question as asked, you can make the test function inline:
procedure Test(OneDigitNumbers: byte); inline;
The compiler will then write the code for Test into each calling function. Whilst you can do this, my advice is that you do not. It's just a trick but I don't think it really helps you.
If you want to raise the exception at the return address, you can do this:
raise Exception.CreateFmt(
'Exception blah blah at %p.',
[ReturnAddress]
) at ReturnAddress;
If you want to go further up the stack, then you'll have to use something like CaptureStackBackTrace. Combine the back trace with raise at and you can raise the exception at any point in the call stack, if really you think that's a good idea. I do not think it's a good idea, as I explain below.
If you use a good debugging tool, like madExcept, then the call stacks in the madExcept bug reports will tell you all you need to know when an error occurs.
With the extra clarification in the comments, it seems that what you really want to happen is for the exception to contain information from higher up the call stack. To my mind it is a violation of encapsulation to ask the callee to report information about its caller. So if you want to include information from the caller, let the caller catch the exception, add the information, and re-raise.
You're looking for a subrange type.
type
TOneDigitNumber = 0..9;
procedure Test(OneDigitNumbers: TOneDigitNumber);
begin
// Do something
end;
begin
Test( 1);
Test( 2);
Test( 9);
Test(12); // compiler error '[DCC Error] MyStuffTest.pas(33): E1012 Constant expression violates subrange bounds
end.
A bit of an elaboration on my comment to your question
type
EMyOwnRangeError = class(ERangeError)
// You can also add your own member variables for easier inspection
public
constructor CreateFrom(const aRangeError: ERangeError);
end;
constructor EMyOwnRangeError.CreateFrom(const aRangeError: ERangeError);
begin
// Do whatever you need to inspect the call stack in aRangeError
// and modify the message and/or set any extra member variable that you
// you define on EMyOwnRangeError.
// No help from me on this, quite simply because I don't have Delphi
// installed on the machine I am currently working at.
end;
procedure MySpecialTest(const aWhatever: Byte);
begin
try
if (aWhatever < 0) or (aWhatever > SOMEUPPERRANGE) then
raise ERangeError.Create;
// Normal code for MySpecialTest
except
on E: ERangeError do raise EMyOwnRangeError.CreateFrom(E);
else
raise; // Make sure other exceptions are propagated.
end;
end;
I have now tested basically the approach in the idea I got from David Heffernan.
I simply added this simple code in one of my units of reuseables:
PROCEDURE TestError(Par:BYTE);
BEGIN
TRY
FINALLY
IF Par>9 THEN Raise Exception.CreateFmt('Error Blah blah blah at ',[Par]) AT #Par;
END;
END;
When this procedure is called with a parameter higher than 9 then it forces an exception.
Delphi ask "Break or Continue" and I click Break.
The result is almost what I would like, but it is so close that I can live with that.
The debugger pops up with a nice red line on the line AFTER the one calling the procedure.
I tried withouth the TRY-Finally-End also, and then it is plain wrong, actually showing the red line another level back from the callstack..
Anyway. I feel that this result is an awful lot better than what I had before. Now my debugging will be a joy rather than a pain.
Thank you :)

It is possible to resume execution from the point where an exception was raised?

There is an example which illustrates my question:
procedure Test;
begin
try
ShowMessage('1');
raise EWarning.Create(12345, 'Warning: something is happens!');
ShowMessage('2');
except
on E: EWarning do
if IWantToContinue(E.ErrorCode) then
E.SkipThisWarning // shows '1' then '2'
else
E.StopExecution; // shows '1'
end;
end;
function IWantToContinue(const ErrorCode: Integer): Boolean;
begin
//...
end;
I tried to use something like this:
asm
jmp ExceptAddr
end;
but it's wont' work...
Any ideas?
Thanks.
No, it is not possible:
There are two kinds of exceptions: logic exceptions that are raised by the programmer using the raise command, and external exceptions that are initiated by the CPU for various conditions: division by zero, stack overflow, access violation. For the first kind, the logic exceptions, there's nothing you can do because they're part of the application "flow". You can't mess with the flow of 3rd party code, you can't even mess with the flow of your own code.
External exceptions
Those are normally raised as a consequence of running a single CPU instruction, when that instruction fails. In Delphi those are made available as EExternal descendants. The list includes access violations, division by zero, stack overflow, privileged instruction and not-so-many others. Theoretically, for some of those exceptions, the causing condition of the exception could be removed and the single CPU instruction retried, allowing the original code to continue as if no error happened. For example SOME access violations might be "fixed" by actually mapping a page of RAM at the address where the error occurred.
There are provisions in the SEH (Structured Exception Handling) mechanism provided by Windows for dealing with such retry-able errors, and Delphi is using SEH under the hood. Unfortunately Delphi doesn't expose the required elements to make this easily accessible, so using them would be very difficult if not impossible. None the less, for particular types of EExternal errors, smart Delphinians might attempt writing custom SEH code and get things working. If that's the case, please ask a new question mentioning the particular type of error you're getting plus the steps you'd like to take to remove the error condition: you'll either get some working code or a customized explanation of why your idea would not work.
Logic exceptions initiated through the use of raise
Most exceptions fall into this category, because most code will check it's inputs before doing potentially dangerous low level stuff. For example, when trying to access an invalid index in a TList, the index would be checked and an invalid index exception raised before attempting to access the requested index. Without the check, accessing the invalid index would either return invalid data or raise an Access Violation. Both of those conditions would be very hard to track errors, so the invalid index exception is a very good thing. For the sake of this question, even if code were allowed to access an invalid index, causing an Access Violation, it would be impossible to "fix" the code and continue, because there's no way to guess what the correct index should be.
In other words, fixing "logic" exceptions doesn't work, shouldn't work, and it's insanely dangerous. If the code that raises the error is yours then you can simply restructure it to NOT raise exceptions for warnings. If that's not your code, then continuing the exception falls into the "insanely dangerous" category (not to mention it's technically not possible). When looking at already written code, ask yourself: would the code behave properly if the raise Exeption were replaced with ShowMessage? The answer should mostly be "NO, the code would fail anyway". For the very rare, very wrong case of 3rd party code that raises an exception for no good reason, you may ask for specific help on patching the code at run-time to NEVER raise the exception.
Here's what could be in some 3rd party code:
function ThirdPartyCode(A, B: Integer): Integer;
begin
if B = 0 then raise Exception.Create('Division by zero is not possible, you called ThirdPartyCode with B=0!');
Result := A div B;
end;
It should be obvious that continuing that code after the exception is not going to allow stuff to "self heal".
Third party code might also look like this:
procedure DoSomeStuff;
begin
if SomeCondition then
begin
// do useful stuff
end
else
raise Exception.Create('Ooops.');
end;
Where would that code "continue"? Quite obviously not the "do usefull stuff" part, unless the code is specifically designed that way.
Those were, of course, simple examples only scratching the surface. From a technical perspective, "continuing" after an exception as you're suggesting is a lot more difficult then jumping to the address of the error. Method calls use stack space to set up local variables. That space was released in the process of "rolling back" after the error, on the way to your exception handler. finally blocks were executed, possibly de-allocating resources where needed. Jumping back to the originating address would be very wrong, because the calling code no longer has what it expects on stack, it's local variables are no longer what they're ment to be.
If it's your code that's raising the exception
Your code can easily be fixed. Use something like this:
procedure Warning(const ErrorText:string);
begin
if not UserWantsToContinue(ErrorText) then
raise Exception.Create(ErrorText);
end;
// in your raising code, replace:
raise Exception.Create('Some Text');
// with:
Warning('Some Text');
AFAIK no. You have to restructure your code to something like
procedure Test;
begin
ShowMessage('1');
try
raise EWarning.Create(12345, 'Warning: something is happens!');
except
on E: EWarning do
if IWantToContinue(E.ErrorCode) then
// shows '1' then '2'
else
raise; // shows '1'
end;
ShowMessage('2');
end;
In C++, it is possible using an SEH __try/__except block whose __except expression evaluates to EXCEPTION_CONTINUE_EXECUTION.
In Delphi, it is not possible to use SEH directly, AFAIK.
BASICally, your code wont work because ExceptAddr is a function, not a variable. So, your code snippet changes as follows:
{$O-}
procedure TForm1.FormCreate(Sender: TObject);
begin
try
OutputDebugString('entering');
raise Exception.Create('Error Message');
OutputDebugString('leaving');
except on E: Exception do
begin
OutputDebugString(PChar(Format('ExceptAddr = %p', [ExceptAddr])));
asm
CALL ExceptAddr
JMP EAX
end;
end;
end;
end;
..you could nest multiple try-except and determine if continue inside each exception:
try
try
some code
except
ret := message('want to continue?');
if ret <> mrYes then
exit;
end;
some other code to perform when no exception or user choose to continue
except
etc..
end;

Delphi OLE - How to avoid errors like "The requested member of the collection does not exist"?

I'm automating Word with Delphi, but some times I got an error message:
The requested member of the collection
does not exist
It seems that the Item member of the Styles collection class does not always exist and some times causes the above mentioned error. My workaround is to catch the exception and skip it, but is there anyway to detect it instead of using the try...except block? The problem with the try...except block is that when debugging the raised exception is annoying...
My code example:
var
aWordDoc: _WordDocument
i: Integer;
ovI: OleVariant;
wordStyle: Style;
begin
for i := 1 to aWordDoc.Styles.Count do
begin
ovI := i;
try
wordStyle := aWordDoc.Styles.Item(ovI);
except
Continue;//skip if any error occurred.
end;
//do something with wordStyle
end;
end
If the compiler accepts it, but it sometimes cannot happen to exist, it is probably IDispatch based latebinding. IDispatch objects can be queried. Maybe carefully working yourself up the tree querying every object for the next would work.
You would then roughly be doing what the compiler does, except that that one throws an exception if somethines doesn't exist. (and if the exception comes in from COM, maybe a slightly different code path can test more).
Sorry to have no readily made code.
I get that message when a bookmark that I'm trying to fill from Word doesn't exist so i have a process that checks first, but I'm not sure the same method would work for you.
procedure MergeData(strBookMark, strData : string);
begin
if WinWord.ActiveDocument.Bookmarks.Exists(strBookMark) = True then
WinWord.ActiveDocument.FormFields.Item(strBookMark).Result := strData;
end;
It has nothing to do with the Item function not being there. The Item function does exists, but the index you give seems to be wrong.
See this msdn article.
An invalid index seems really weird, because you are performing a for loop from 1 to Styles.Count. So if there is no Style, you should not enter the loop.
The only plausible explanation I can think of is that while you are in your loop, the Styles.Count changes and you are getting out of bounds. Are you deleting styles in your loop perhaps? Try a loop going from Styles.Count downto 1 or try a While loop, evaluating Styles.Count at every iteration.
Other things I can think of, but are very unlikely:
While assigning I to ovI, it gets converted to an OleString, so Word searches for a style named "I", instead of a Style at I
While assigning I to ovI, something in the conversion goes wrong and it gets in the range of $FFFFFFA5 - $FFFFFFFF, which are constants for Builtin styles.
try checking if it is null or not with a IF statement

Delphi Exception Handling - How to clean up properly?

I'm looking at some code in an application of ours and came across something a little odd from what I normally do. With exception handling and cleanup, we (as well as many other programmers out there, I'm sure) use a Try/Finally block embedded with a Try/Except block. Now I'm used to the Try/Except inside the Try/Finally like so:
Try
Try
CouldCauseError(X);
Except
HandleError;
end;
Finally
FreeAndNil(x);
end;
but this other block of code is reversed as so:
Try
Try
CouldCauseError(X);
Finally
FreeAndNil(x);
end;
Except
HandleError;
end;
Looking around the web, I'm seeing folks doing this both ways, with no explanation as to why. My question is, does it matter which gets the outside block and which gets the inside block? Or will the except and finally sections get handled no matter which way it is structured? Thanks.
One difference is that try..finally..except is potentially vulnerable to an exception masking situation.
Imagine that an exception occurs in CouldCauseError(). Then imagine that the attempt to FreeAndNIL(X) in the finally causes a further exception. The original exception (quite possibly which lead to the instability leading to the FreeAndNIL() exception) is lost. The except handler is now handling the "downstream" exception that occured after the original one.
try..except..finally avoids this of course and should be preferred for this reason (deal with exceptions as close as possible to their source).
The other way to handle a simple case such as this (a single object being cleaned) is to include the cleanup both in the normal flow and in the exception handler:
try
CouldCauseError(X);
FreeAndNil(x);
except
HandleError;
FreeAndNil(x);
end;
This looks a little scary at first ("I need to be SURE that FreeAndNIL(X) is called, so I HAVE TO HAVE A FINALLY!!") but the only way that the first FreeAndNIL() might not be called is if there is an exception and if there is an exception you are FreeAndNIL()ing as well anyway, and it makes the order of cleanup in the event of an exception a little clearer (in the sense of removing noise that to some extent has to be "filtered" out in order to understand what is going on).
But, I personally do not like it - if you change code in either the exception handler or the normal flow you risk breaking the cleanup behaviour, but depending on the code around such a block, and the size of the block itself, the reduction in "noise" can be argued to be justified in some cases, for the sake of simplification.
However, this relies on the fact that FreeAndNIL() is actually "NILThenFree()"... X is NIL'd before it is Free'd, so if an exception occurs in the FreeAndNIL(X) in the normal flow, then X will be NIL when the exception handler catches the exception raised by X.Free, so it will not attempt to "double-free" X.
Whatever you decide, I hope that helps.
The finally and except will both trigger, the order is up to you. It depends on what you want to do in your finally or except block. Do you want to free something that is used in the except block? Place finally around the except block.
It all depends if the code in your finally block can raise an exception itself (then it needs to be protected by an upper level try except), or if you need something in your exception handling that should be freed later (then it needs to be freed in an upper level finally block).
All that means that sometimes you can even have some code like:
try
try
try
CouldCauseError(X);
except
HandleErrorWith(X);
end;
finally
FreeAndNil(X); // and/or any resource cleanup
end;
except
CatchAllError;
end;
At first your code looks a little bit strange. I miss the creation of X.
X := CreateAnX
try
DoSomeThing(X);
finally
FreeAndNil(x);
end;
It is important. Because if you have code like this
// don't do something like this
try
X := CreateAnX
DoSomeThing(X);
finally
FreeAndNil(x);
end;
you can be lucky and it works. But if the construction fails you can be "lucky" and get an access violation or you have bad luck and get an access violation some times later at an completely different code position.
An alternative could be
X := nil;
try
X := CreateAnX
DoSomeThing(X);
finally
FreeAndNil(x);
end;
Where to use except depends on what is your intention. When you want to catch every exception and know all calling code clean its problems (using try finally) then an outer except-block is the way to go
try
X := CreateAnX
try
DoSomeThing(X);
finally
FreeAndNil(x);
end;
except
on e: Exception do
LogException(e)
end;
But always think about it when you want catch all errors. As an example (and I often see it wrong) don't do it in an Indy OnExecute-handler this way. There you must use something like this
try
X := CreateAnX
try
DoSomeThing(X);
finally
FreeAndNil(x);
end;
except
on EIdException do
raise;
on e: Exception do
LogException(e)
end;
If you expect an exception because you throw it or (as an example) a conversation can fail, look for the most inner position to catch the error:
X := CreateAnX
try
DoSomeThing(X);
try
i := StrToInt(X.Text);
except
on EConvertError do
i := 0;
end;
finally
FreeAndNil(x);
end;
don't do it this way
X := CreateAnX
try
try
DoSomeThing(X);
i := StrToInt(X.Text);
except
on EConvertError do
i := 0;
end;
finally
FreeAndNil(x);
end;
Do this only if you expect an EConvertError in DoSomeThing too. If DoSomeThing throws an EConvertError and you don't expect it, your code has a serious problem which need to be corrected. In this situation ensure that the user can save his work (perhaps as a copy, because his work could be damaged) and ensure you get the info about the problem.
At least try except is for handling Exceptions not for hiding them.

Resources