What is the correct way to nest exceptions? - Using Delphi

跟風遠走 提交于 2019-12-23 02:38:26

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!