Обработка Delphi Exception - Как правильно очистить?
Я смотрю какой-то код в нашем приложении и натыкаюсь на что-то немного странное из того, что я обычно делаю. С обработкой исключений и очисткой мы (как и многие другие программисты там, я уверен) используют блок Try/finally, встроенный в блок Try/Except. Теперь я привык к Try/Except внутри Try/finally следующим образом:
Try
Try
CouldCauseError(X);
Except
HandleError;
end;
Finally
FreeAndNil(x);
end;
но этот другой блок кода обращается так:
Try
Try
CouldCauseError(X);
Finally
FreeAndNil(x);
end;
Except
HandleError;
end;
Оглядываясь по Сети, я вижу, как люди делают это в обоих направлениях, без объяснения причин. Мой вопрос в том, имеет ли значение, который получает внешний блок и который получает внутренний блок? Или будут обрабатываться исключения и, наконец, разделы, независимо от того, каким образом он структурирован? Спасибо.
Ответы
Ответ 1
Одно отличие заключается в том, что try..finally..except потенциально уязвим для ситуации маскировки исключения.
Представьте, что исключение происходит в CouldCauseError(). Тогда представьте, что попытка FreeAndNIL (X) в наконец вызывает дополнительное исключение. Исходное исключение (вполне возможно, что приводит к нестабильности, приводящей к исключению FreeAndNIL()), теряется. Обработчик кроме теперь обрабатывает исключение "ниже по течению", которое произошло после исходного.
try..except..finally избегает этого, конечно, и должно быть предпочтительным по этой причине (иметь дело с исключениями как можно ближе к их источнику).
Другим способом обработки простого случая, такого как этот (очищаемый единственный объект), является включение очистки как в нормальный поток , так и в обработчик исключений:
try
CouldCauseError(X);
FreeAndNil(x);
except
HandleError;
FreeAndNil(x);
end;
Вначале это выглядит немного страшно ( "Мне нужно УВЕРЕНЫ, что вызывается FreeAndNIL (X), поэтому я должен иметь НАКОНЕЦ!!" ) но единственный способ, которым не может быть вызван первый FreeAndNIL(), - это если есть исключение, и если в любом случае есть исключение, вы также FreeAndNIL() ing, и он делает порядок очистки в случае исключения a (в смысле устранения шума, который в какой-то степени должен быть "отфильтрован", чтобы понять, что происходит).
Но мне лично это не нравится - если вы меняете код в обработчике исключений или в обычном потоке, вы рискуете нарушить поведение очистки, но в зависимости от кода вокруг такого блока и размера самого блока, сокращение "шума" можно утверждать оправданным в некоторых случаях для упрощения.
Однако это зависит от того, что FreeAndNIL() на самом деле " NILThenFree()"... X - NIL'd прежде чем он будет Free'd, поэтому, если исключение возникает в FreeAndNIL (X) в нормальном потоке, тогда X будет NIL, когда обработчик исключений поймает исключение, вызванное X.Free, поэтому он не будет пытаться "удвоить" X.
Что бы вы ни решили, я надеюсь, что это поможет.
Ответ 2
Наконец, и за исключением того, что оба будут запускаться, заказ зависит от вас. Это зависит от того, что вы хотите сделать в своем блоке finally или except. Вы хотите освободить что-то, что используется в блоке исключения? Поместите наконец вокруг исключающего блока.
Ответ 3
Все зависит от того, сможет ли код в вашем блоке finally создать само исключение (тогда он должен быть защищен при попытке верхнего уровня, кроме), или если вам что-то нужно в вашей обработке исключений, должен быть освобожден позже (тогда он должен быть освобожден на верхнем уровне, наконец, заблокировать).
Все это означает, что иногда у вас может даже быть некоторый код:
try
try
try
CouldCauseError(X);
except
HandleErrorWith(X);
end;
finally
FreeAndNil(X); // and/or any resource cleanup
end;
except
CatchAllError;
end;
Ответ 4
Сначала ваш код выглядит немного странно. Я пропустил создание X.
X := CreateAnX
try
DoSomeThing(X);
finally
FreeAndNil(x);
end;
Это важно. Потому что, если у вас есть код, подобный этому
// don't do something like this
try
X := CreateAnX
DoSomeThing(X);
finally
FreeAndNil(x);
end;
вы можете быть счастливым, и он работает. Но если сбой конструкции, вы можете быть " удачливым" и получить нарушение доступа или у вас неудача и получить нарушение доступа несколько раз позже в совершенно другой позиции кода.
Альтернативой может быть
X := nil;
try
X := CreateAnX
DoSomeThing(X);
finally
FreeAndNil(x);
end;
Где использовать except зависит от ваших намерений. Если вы хотите поймать каждое исключение и знать, что весь код вызова очищает его проблемы (используя try finally), то внешний исключающий блок - это путь
try
X := CreateAnX
try
DoSomeThing(X);
finally
FreeAndNil(x);
end;
except
on e: Exception do
LogException(e)
end;
Но всегда думайте об этом, когда хотите поймать все ошибки. В качестве примера (и я часто вижу это неправильно) не делайте этого в обработчике Indy OnExecute таким образом. Там вы должны использовать что-то вроде этого
try
X := CreateAnX
try
DoSomeThing(X);
finally
FreeAndNil(x);
end;
except
on EIdException do
raise;
on e: Exception do
LogException(e)
end;
Если вы ожидаете исключение, потому что вы его бросаете или (как пример), разговор может потерпеть неудачу, найдите самую внутреннюю позицию, чтобы поймать ошибку:
X := CreateAnX
try
DoSomeThing(X);
try
i := StrToInt(X.Text);
except
on EConvertError do
i := 0;
end;
finally
FreeAndNil(x);
end;
не делайте этого так.
X := CreateAnX
try
try
DoSomeThing(X);
i := StrToInt(X.Text);
except
on EConvertError do
i := 0;
end;
finally
FreeAndNil(x);
end;
Сделайте это, только если вы ожидаете EConvertError в DoSomeThing. Если DoSomeThing выбрасывает EConvertError, и вы этого не ожидаете, ваш код имеет серьезную проблему, которую необходимо исправить. В этой ситуации убедитесь, что пользователь может сохранить свою работу (возможно, в качестве копии, поскольку его работа может быть повреждена) и убедитесь, что вы получили информацию о проблеме.
По крайней мере, попробуйте, за исключением обработки исключений, которые не скрывают их.