嵌套异常的正确方法是什么? - 使用Delphi

时间:2015-12-10 19:13:22

标签: delphi exception-handling

假设我有三个(或更多)程序,其中一些程序互相调用,如下所示,其中任何一个程序都可能失败。

如果其中任何一个失败,我希望'main'程序立即记录失败并终止程序。

在Delphi中使用哪种正确的语法将异常“传回”到每个先前的过程调用?

如果有人可以帮助我获取主程序的Try / except块以确定哪个位失败,那就更好了!

三个程序的示例伪代码和主程序可能如下所示。

(我认为我理解这个原则,与'加注'有关,但是想要一些实际语法和我应该使用的代码的帮助)

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

3 个答案:

答案 0 :(得分:7)

解决这个问题的最佳方法是删除所有这些异常处理程序。使用像madExcept,EurekaLog,JCL Debug等库来记录任何异常,使其一直回到顶级异常处理程序。

尝试为程序中的每个函数添加异常处理程序是站不住脚的。这根本不是如何使用异常的。作为一个广泛的规则,您应该将异常视为不应被捕获的事物。它们代表了异常行为,因此,通常情况下,它们被引发的函数不知道如何处理它们。

所以,停止尝试处理异常。作为指导原则,不要处理它们。如果他们一直到顶级异常处理程序,那么在那里处理它们。如果你使用上面提到的一个库,你将能够获得丰富的调试信息,以帮助你理解为什么首先引发异常。

答案 1 :(得分:6)

记录后,让每个函数重新提升捕获的异常,例如:

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.

如果您希望主过程识别WHICH函数失败,您需要将该信息传递到异常链中,例如:

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.

或者:

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 :(得分:3)

来自其他语言的开发人员花费大量时间和精力担心“未处理的异常”,但在典型的Delphi表单应用程序中,如果查看Application.Run背后的代码,您将看到如果您让所有异常都将被处理他们一直冒泡到顶端。 (除非你有充分的理由干涉,否则这是首选行为)

通常,最好在异常中添加更多信息,然后重新提升它,即。放手吧。出了点问题,你的调用函数需要知道,这首先是异常的目的。

如果您想记录每个错误,那么一个好的地方就是在Application.OnException事件中。 NB。您的示例是DOS样式的命令行应用程序,而不是典型的Delphi Windows窗体应用程序,不确定这是否是您的意图。如果这只是为了让示例保持简单,那么您实际上已经为自己创建了更多的工作,因为您无权访问Application对象以及随之而来的所有功能。

例如

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; 

直接回答你的问题:

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.