2015-12-10 7 views
1

Предположим, у меня есть три (или более) процедуры, некоторые из которых называют друг друга, как показано ниже, любой из которых может выйти из строя.Каков правильный способ вложения исключений? - Использование Delphi

Если какой-либо из них не работает, я хочу, чтобы «главная» программа немедленно регистрировала сбой и завершала программу.

Каков правильный синтаксис для использования в 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. 

ответ

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. 

Если вы хотите основную процедуру, чтобы определить, какие функции не удалось, вам нужно передать эту информацию по цепочке исключения, например:

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. 
+0

application.terminate есть, потому что код, где все это может быть запущена либо вручную, либо, если программа называется с помощью запланированного задания, передавая параметр. (Я тестирую, если это так и заканчивается, если оно есть). В настоящий момент у меня возникают проблемы, потому что если произойдет исключение, программа может висеть, а затем в следующий раз, когда планировщик попытается запустить задание, которое не работает, когда работа уже запущена. Таким образом, идея состоит в том, чтобы убедиться, что если он запускается с параметром командной строки, тогда любое исключение заставляет программу прекращать работу правильно и в идеале, запишите, почему она не удалась. – user3209752

+0

'Application.Terminate()' просто отправляет сообщение 'WM_QUIT' в очередь сообщений вызывающего потока. 'Application.Run()' запускает фактический цикл сообщения. Если вы не вызываете 'Application.Run()' или иначе запускаете свой собственный цикл сообщений, 'Application.Terminate()' бесполезен. Просто выйдите из основного блока 'begin/end.', чтобы завершить процесс. И сами исключения не вызывают зависаний. Ваш код должен был бы что-то делать в ответ на пойманное исключение, чтобы зависать, например, отображать окно модального всплывающего окна, которое пользователь должен (но не сможет) уволить. –

+0

Спасибо Remy, очень полезный материал - и несколько указателей на то, где моя программа может быть неудачной. У меня действительно есть несколько сообщений, чтобы показывать исключения для интерактивного пользователя. Бьюсь об заклад, один из них показывает, когда он запускается как запланированное задание. Трудно узнать, кому отдать ответ, вы или Дэвид, поскольку вы оба всегда очень полезны. В этом случае, как говорит сам Давид, он дал мне очень полезный совет, но вы ответили на вопрос напрямую, поэтому я думаю, что победитель должен быть Реми для ответа на этот вопрос. Благодаря обоим. – user3209752

6

Лучший способ справиться с этим, чтобы удалить все из этих обработчиков исключений. Используйте библиотеку, такую ​​как madExcept, EurekaLog, JCL Debug и т. Д. Для регистрации любых исключений, которые полностью возвращаются к обработчику исключений верхнего уровня.

Это просто несостоятельно для вас, чтобы попытаться добавить обработчик исключений для каждой функции в вашей программе. Это совсем не так, как предполагается использование исключений. Как правило, вы должны рассматривать исключения как вещи, которые не должны быть пойманы. Они представляют собой исключительное поведение, и, как правило, обычно бывает, что функция, в которой они созданы, не знает, как с ними бороться.

Итак, перестаньте обрабатывать исключения. Не обращайтесь с ними, как руководящий принцип. Если они дойдут до верхнего уровня обработчика исключений, побеседуйте с ними. И если вы используете одну из вышеупомянутых библиотек, вы сможете получить богатую отладочную информацию, чтобы помочь вам понять, почему в первую очередь возникло исключение.

+3

Удивительное понижение, которое я надеюсь свести на нет. – MartynA

+0

@ Давид, Пожалуйста, объясните, что вы имеете в виду? Вы имеете в виду, что у вас есть попытка, кроме блока в главной программе, и больше нигде? Я ценю комментарии от себя и Реми, поскольку вы оба знаете, о чем говорите, но вы, похоже, различаетесь в отношении opioion на этом – user3209752

+4

Вы должны ** обрабатывать ** исключение, если ** действительно можете обрабатывать ** его. Не используйте слепые следы всех исключений. –

3

Разработчики с других языков тратят много времени и энергии на беспокойство об «необработанных исключениях», но в типичном приложении форм Delphi, если вы посмотрите на код, стоящий за Application.Run, вы увидите, что все исключения будут обработаны, если вы пусть они пузырятся до самого верха. (Что является предпочтительным поведением, если у вас нет оснований вмешиваться)

Довольно часто рекомендуется добавлять дополнительную информацию в исключение, а затем повторно поднимать ее, т.е. отпусти ситуацию. Что-то пошло не так, ваша функция вызова должна знать, и в этом и заключается цель исключения.

Если вы хотите регистрировать каждую ошибку, тогда хорошее место для этого было бы в событии Application.OnException. NB. ваш пример - это приложение командной строки в стиле DOS, а не типичное приложение для форм Windows Delphi, не уверен, что это то, что вы намеревались.Если бы это было просто для того, чтобы попытаться упростить пример, вы фактически создали больше работы для себя, так как у вас нет доступа к объекту 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. 
+0

Да, этот код был упрощен для этого форума. Это актуально в событии OnShow основной формы. У меня есть опция меню, чтобы повторно заполнить одну базу данных с другой внешней, загрузить материал с онлайн-сервера, сделать много математики и сохранить резюме в моей базе данных. Любые сообщения об ошибках здесь отображаются пользователю.Тем не менее, программа также может вызываться планировщиком с параметром командной строки для выполнения всей этой работы за одну ночь. Я обнаружил это в начале OnShow, и в этом случае мне нужно, чтобы этот раздел кода был защищен от бомб, регистрировал любые ошибки, поэтому я знаю, где он поступил неправильно, и всегда изящно закрывается. – user3209752

+0

Хорошо, тогда вам просто нужно зарегистрировать ошибку в событии Application.OnException, вот и все, больше нигде. Я добавил пример использования Application.OnException в свой первоначальный ответ. – Maya

+0

Пример кода немного неясен - иногда он использует «E.Message: = E.Message +», а иногда и E.Message: = «некоторое значение», которое - в зависимости от дерева вызовов - отменяет существующую информацию. Я думаю, было бы лучше иметь строковый список или добавить полные данные «внутреннего» исключения в следующем исключении более высокого уровня. – mjn

Смежные вопросы