问题
Assume I have three (or more) procedures, some of which call each other, as shown below, any one of which can fail.
If any one of them do fail I want the 'main' program to immediately log the failure and terminate the program.
What is the correct syntax to use in Delphi to 'pass back' an exception to each preceeding procedure call?
Even better if someone can help me to get the main program's Try/except block to identify which bit failed!
Sample pseudo code of the three procedures and the main program might look like below.
(I think I understand the principle, something to do with 'raise', but would like some help with the actual syntax and what code I should use)
//////////////////////////////////////
Procedure DoProcA
begin
try
begin
{stuff}; //stuff that might fall
end;
except
on E : Exception do
begin
LogError ('error in A');
end //on E
end;//try
//////////////////////////////////////
Procedure DoProcB
begin
try
begin
Do ProcC; //another proc that might fail
{other stuff}
end;
except
on E : Exception do
begin
LogError ('error in B');
end //on E
end;//try
//////////////////////////////////////
Procedure DoProcC
begin
try
begin
{Do stuff} //even more stuf fthat might fail
end;
except
on E : Exception do
begin
LogError ('error in C');
end //on E
end;//try
//////////////////////////////////////
//Main programo
begin
try
DoProcA;
DoProcB;
{other stuff}
except
{here I want to be able to do something like
if failure of A, B or C then
begin
LogError ('Failure somewhere in A, B or C');
application.terminate;
end;}
end; //try
end.
回答1:
Have each function re-raise the caught exception after logging it, eg:
Procedure DoProcA;
begin
try
{stuff}; //stuff that might fall
except
on E : Exception do
begin
LogError ('error in A');
raise; // <-- here
end;
end;
end;
Procedure DoProcB;
begin
try
DoProcC; //another proc that might fail
{other stuff}
except
on E : Exception do
begin
LogError ('error in B');
raise; // <-- here
end;
end;
end;
Procedure DoProcC;
begin
try
{Do stuff} //even more stuff that might fail
except
on E : Exception do
begin
LogError ('error in C');
raise; // <-- here
end;
end;
end;
begin
try
DoProcA;
DoProcB;
{other stuff}
except
on E: Exception do
begin
LogError ('Failure somewhere in A, B or C');
//Application.Terminate; // this is not useful unless Application.Run is called first
end;
end;
end.
If you want the main procedure to identify WHICH function failed, you need to pass that information down the exception chain, eg:
type
MyException = class(Exception)
public
WhichFunc: String;
constructor CreateWithFunc(const AWhichFunc, AMessage: String);
end;
constructor MyException.CreateWithFunc(const AWhichFunc, AMessage: String);
begin
inherited Create(AMessage);
WhichFunc := AWhichFunc;
end;
Procedure DoProcA;
begin
try
{stuff}; //stuff that might fall
except
on E : Exception do
begin
raise MyException.CreateWithFunc('DoProcA', E.Message); // <-- here
end;
end;
end;
Procedure DoProcB;
begin
try
DoProcC; //another proc that might fail
{other stuff}
except
on E : MyException do
begin
raise; // <-- here
end;
on E : Exception do
begin
raise MyException.CreateWithFunc('DoProcB', E.Message); // <-- here
end;
end;
end;
Procedure DoProcC;
begin
try
{Do stuff} //even more stuff that might fail
except
on E : Exception do
begin
raise MyException.CreateWithFunc('DoProcC', E.Message); // <-- here
end;
end;
end;
begin
try
DoProcA;
DoProcB;
{other stuff}
except
on E: MyException do
begin
LogError ('Failure in ' + E.WhichFunc + ': ' + E.Message);
end;
on E: Exception do
begin
LogError ('Failure somewhere else: ' + E.Message);
end;
end;
end.
Or:
type
MyException = class(Exception)
public
WhichFunc: String;
constructor CreateWithFunc(const AWhichFunc, AMessage: String);
end;
constructor MyException.CreateWithFunc(const AWhichFunc, AMessage: String);
begin
inherited Create(AMessage);
WhichFunc := AWhichFunc;
end;
Procedure DoProcA;
begin
try
{stuff}; //stuff that might fall
except
on E : Exception do
begin
raise MyException.CreateWithFunc('DoProcA', E.Message); // <-- here
end;
end;
end;
Procedure DoProcB;
begin
try
DoProcC; //another proc that might fail
{other stuff}
except
on E : Exception do
begin
Exception.RaiseOuterException(MyException.CreateWithFunc('DoProcB', E.Message)); // <-- here
end;
end;
end;
Procedure DoProcC;
begin
try
{Do stuff} //even more stuff that might fail
except
on E : Exception do
begin
raise MyException.CreateWithFunc('DoProcC', E.Message); // <-- here
end;
end;
end;
var
Ex: Exception;
begin
try
DoProcA;
DoProcB;
{other stuff}
except
on E: Exception do
begin
Ex := E;
repeat
if Ex is MyException then
LogError ('Failure in ' + MyException(Ex).WhichFunc + ': ' + Ex.Message)
else
LogError ('Failure somewhere else: ' + Ex.Message);
Ex := Ex.InnerException;
until Ex = nil;
end;
end;
end.
回答2:
The best way to deal with this is to remove all of these exception handlers. Use an library like madExcept, EurekaLog, JCL Debug etc. to log any exceptions that make it all the way back to the top level exception handler.
It's simply untenable for you to attempt to add an exception handler to each and every function in your program. That's not at all how exceptions are meant to be used. As a broad rule, you should regard exceptions as things that should not be caught. They represent exceptional behaviour, and as such, it is usually the case that the function where they are raised does not know how to deal with them.
So, stop trying to handle exceptions. Don't handle them, as a guiding principle. If they make it all the way to the top level exception handler, deal with them there. And if you use one of the libraries mentioned above you will be able to obtain rich debugging information to help you understand why the exception was raised in the first place.
回答3:
Developers from other languages spend a lot of time and energy worrying about "unhandled exceptions" but in a typical Delphi forms application, if you look at the code behind Application.Run you will see that all exceptions will get handled if you let them bubble all the way to the top. (Which is the preferred behaviour, unless you have good reason to interfere)
Quite often, it is a good idea to add more information to the exception, and then re-raise it, ie. let it go. Something went wrong, your calling function needs to know, and that is the purpose of an exception in the first place.
If you are wanting to log each and every error, then a good place to do that would be in the Application.OnException event. NB. your example is a DOS style command line application, not a typical Delphi Windows forms application, not sure if that is what you intended. If this was just to try keep the example simple, you have actually created more work for yourself, as you don't have access to the Application object and all the functionality that goes with that.
Eg.
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnException := AppException;
end;
procedure TForm1.AppException(Sender: TObject; E: Exception);
begin
if RunningInAutomationMode then
begin
LogError(E.Message);
Application.Terminate;
end
else
Application.ShowException(E);
end;
To answer your question directly though:
Procedure DoProcA;
begin
try
{stuff}; //stuff that might fall
except
on E : Exception do
begin
//LogError ('error in A'); will get logged later, don't want to log twice
E.Message := 'The following error occurred while trying to do whatzit to a whozit: '+E.Message;
raise;
end;
end;
end;
Procedure DoProcB;
begin
try
DoProcC; //another proc that might fail
{other stuff}
except
on E : Exception do
begin
//LogError ('error in B');
E.Message := E.Message + ' (Proc B)';
raise;
end;
end;
end;
Procedure DoProcC;
begin
try
{Do stuff} //even more stuff that might fail
except
on E : Exception do
begin
//LogError ('error in C');
E.Message := 'The following error occurred during procedure C: '+E.Message;
raise; //Note: do not use raise Exception.Create(E.Message); as you will then lose the exception's type, which can be very useful information to have
end;
end;
end;
begin
try
DoProcA;
DoProcB;
{other stuff}
except
on E: Exception do
begin
LogError (E.Message); //this will end up logging all the exceptions, no mater which procedure they occurred in
//Exception has not been re-raised, so code will continue from this point
Exit;
end;
end;
{Some more code} //Called exit above, so that this code won't get called, although it is unlikely you'll have more code outside the try..except block
end.
来源:https://stackoverflow.com/questions/34209877/what-is-the-correct-way-to-nest-exceptions-using-delphi