2017-02-15 2 views
4

У меня была дискуссия на днях: https://stackoverflow.com/a/42156860/937125 , где я не совсем понял, почему Abort был лучше, чем позвонить Exit в этой ситуации. Я стараюсь не использовать его в потоке кода. Я считаю это плохой практикой и плохим для потока кода. но @ заявление Давида в комментариях заставило меня задаться вопросом, если возможно, я чего-то не хватает:Использование Abort для улучшения/упрощения кода в некоторых ситуациях

Без молчаливого исключения, как бы вы прервать операцию, когда глубоко вниз стека вызовов. Например, как бы вы прекратили копирование файла с использованием 10 глубоких стеков вызовов? Разве это не то, для чего предназначены исключения для ? Конечно, вы можете закодировать его без исключений , но он гораздо более подробный и подверженный ошибкам.

Я не могу представить такую ​​ситуацию. Может ли кто-нибудь дать мне пример такого кода/сценария и убедить меня в том, что Abort в приведенном выше случае действительно хорошая вещь и «гораздо более подробный и подверженный ошибкам». (Для иллюстрации достаточно 3-4 стека вызовов)

+0

Я действительно сочувствую вам, поскольку вы видели, что я тот, кто предложил функцию выхода, и Дэвид упомянул об этом ... «Случается, что вы иногда извлекаете код в другой метод, а затем выходите только отменяет этот метод. Abort устойчив к таким изменениям », и это не придало мне больше света. – Fero

+0

@Fero Method Метод вызовов B вызывает метод C. Метод C делает ранний выход. Но тогда B должен сделать то же самое. И так же A. –

+0

Хорошо, но это будет только зависеть от того, что делает ваш метод? потому что, если ваш метод ничего не возвращает, я не вижу проблемы ... – Fero

ответ

7

Самый простой сценарий, который иллюстрирует мою точку зрения, как так:

procedure MethodA; 
begin 
    MethodB; 
    MethodC; 
end;  

procedure MethodB; 
begin 
    // ... do stuff 
end; 

procedure MethodC; 
begin 
    // ... do stuff 
end; 

Это прекрасно, как это. Теперь предположим, что MethodB запрашивает у пользователя некоторый ввод, и если пользователь нажимает кнопку Cancel, то дальнейшая работа не должна выполняться. Вы могли бы осуществить это так:

procedure MethodA; 
begin 
    if MethodB then 
    MethodC; 
end;  

function MethodB: Boolean; 
begin 
    Result := MessageDlg(...)=mrOK; 
    if not Result then 
    exit; 
    // ... do stuff 
end; 

procedure MethodC; 
begin 
    // ... do stuff 
end; 

Это прекрасно работает, но представьте себе, что вы в реальном мире кодом, был глубже вложенность. Логическое значение, возвращаемое MethodB, возможно, должно быть передано на большое количество уровней. Это станет громоздким.

Или подумайте, что произойдет, если MethodB должно вернуть значение своему вызывающему абоненту. В этом сценарии исходный код может быть таким:

procedure MethodA; 
begin 
    MethodC(MethodB); 
end;  

function MethodB: string; 
begin 
    Result := ...; 
end; 

procedure MethodC(Value: string); 
begin 
    // ... do stuff with Value 
end; 

Теперь еще раз подумайте, что произойдет, если пользователь получит шанс отменить. Как мы можем вернуть логическую строку и строку из MethodB? Используя параметр out для одного из возвращаемых значений? Использование составной структуры, такой как запись, для переноса обоих значений. Последнее, очевидно, включает в себя множество шаблонов, поэтому давайте рассмотрим первое.

procedure MethodA; 
var 
    Value: string; 
begin 
    if MethodB(Value) then 
    MethodC(Value); 
end;  

function MethodB(out Value: string): Boolean; 
begin 
    Result := MessageDlg(...)=mrOK; 
    if not Result then 
    exit; 
    Value := ...; 
end; 

procedure MethodC(Value: string); 
begin 
    // ... do stuff with Value 
end; 

Конечно вы можете сделать это, но это начинает выглядеть как своего рода код, который исключения были разработаны для упрощения. И на этом этапе рассмотрим существование молчащего исключения, EAbort, поднятого вызовом Abort, что не приводит к сообщению, отображаемому обработчиком исключений верхнего уровня. Последняя точка - это то, что имеется в виду под номером silent.

Теперь код становится:

procedure MethodA; 
begin 
    MethodC(MethodB); 
end;  

function MethodB: string; 
begin 
    if MessageDlg(...)<>mrOK then 
    Abort; 
    Result := ...; 
end; 

procedure MethodC(Value: string); 
begin 
    // ... do stuff with Value 
end; 

Преимущество заключается в том, что MethodA не нужно беспокоиться об отмене. И если стек вызовов был глубже, ни один из методов между MethodA в верхней части и MethodB в момент ввода пользователя не должен был знать ничего об отмене.

Дополнительным преимуществом является то, что MethodB может сохранять свою естественную подпись. Он возвращает string. В случае отказа, либо от более традиционного исключения, либо от отмены пользователя, генерируется исключение.

Этот очень простой пример не настолько убедительнее, чем предыдущий, который не использует Abort. Но представьте, как выглядел бы код, если бы MethodB были на 4 или 5 глубины в стеке вызовов?


Я абсолютно не говорю, что Abort всегда должен использоваться вместо exit. Я убежден, что оба имеют свое место. Где Abort shines - это когда пользователь выбирает отмену операции, и вы не хотите, чтобы в текущем обработчике событий больше не выполнялась обработка. Кроме того, поскольку пользователь явно отказался от отмены, дальнейший пользовательский интерфейс не должен быть представлен им. Вам не требуется окно с сообщением пользователю о том, что они отменены, они уже знают это.

+1

Извините, Дэвид, это оставляет меня еще более смущенным. Что еще должно прекратить молчаливое исключение? (Я знаю, что вы сказали, что MethodA - это высший уровень, но на практике это не обязательно). Вам не нужна попытка, кроме конструкции на верхнем уровне, к которой вы хотите пробиться? Может быть, не в этом искусственном примере, но он, несомненно, станет более ясным? – Dsm

+0

@Dsm В инфраструктуре VCL существует обработчик исключений верхнего уровня, который ловит и игнорирует 'EAbort'. Очевидно, вам нужно что-то сделать. Вся концепция исключений падает, если вы не можете доверять исключениям, которые нужно поймать и обработать в нужном месте. –

+0

Да, Давид, я это знаю. Я думаю, что тебе не хватает моей точки. Если MethodA - это то место, где вы хотите его остановить, а MethodA находится на полпути к стеку вызовов, то что это значит, чтобы остановить его до конца до рамки VCL? Я просто почувствовал, что это может быть яснее. – Dsm

4

Предположите, что ваша программа выполняет длительную операцию либо в отдельном потоке, либо (хотя и на него нахмурилась), вызывая Application.ProcessMessages. Теперь вы хотите, чтобы пользователь мог безопасно прервать эту операцию (то есть: все ресурсы очищены, данные находятся в согласованном состоянии и т. Д.). Таким образом, пользовательский интерфейс устанавливает флаг где-то и в вашем коде вы периодически проверяете этот флаг. Если он установлен, вы вызываете Abort или явно поднимаете EAbort. Это приведет к тому, что все тщательно обработанные блоки try/except/finally будут выполнены и убедитесь, что отмена операции безопасна.

// in the main thread: 
procedure TMyProgressDialog.b_AbortClick(Sender: TObject); 
begin 
    if AskUserIfHeIsSure then begin 
    gblAbortedFlag := true; 
    b_Abort.Enabled := false; 
    b_Abort.Caption := _('Aborting'); 
    end; 
end; 

// call this repeatedly during the lenghty operation: 
procecdure CheckAborted; 
begin 
    // If you are in the main thread, you might want to call 
    // Application.ProcessMessages; 
    // here. If not, definitely don't. 
    if gblAbortedFlag then 
    Abort; 
end; 

Конечно, это может быть сделан с другим исключением, но я не могу думать о каком-либо другом способе, чтобы безопасно выйти из глубокого стека вызовов без необходимости программировать много МФСА и выходов.

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