Обработка 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, и вы этого не ожидаете, ваш код имеет серьезную проблему, которую необходимо исправить. В этой ситуации убедитесь, что пользователь может сохранить свою работу (возможно, в качестве копии, поскольку его работа может быть повреждена) и убедитесь, что вы получили информацию о проблеме.

По крайней мере, попробуйте, за исключением обработки исключений, которые не скрывают их.