Зачем использовать ключевое слово finally?

Я понимаю, для чего используется ключевое слово "finally" на разных языках, однако я с трудом понимаю, почему вы бы использовали его, если бы не было предпочтения форматирования по вкусу.

Например, в PHP:

try {
  possibleErrorThrownFunction();
}
catch (CustomException $customException) {
  // handle custom error
}
catch (Exception $exception) {
  // handle the error
}
finally {
  // run this code every single time regardless of error or not
}

Какая разница между тем, что делает этот код, и этим?

try {
  possibleErrorThrownFunction();
}
catch (CustomException $customException) {
  // handle custom error
}
catch (Exception $exception) {
  // handle the error
}

// run this code every single time regardless of error or not

Разве эта последняя строка не всегда запускается из-за ошибки? В таком случае, нет никакого случая, чтобы действительно использовать finally, если вы просто не хотите поддерживать форматирование в стиле кода?

Пример случая, когда инструкция finally необходима и отлична от простого размещения кода после операторов try/catch, будет полезна, если я что-то здесь упустил.

Ответы

Ответ 1

Короткий ответ

Блоки Finally гарантированно будут работать независимо от того, что происходит внутри блоков try и catch, до того, как произойдет сбой программы.

Это как бы объяснено здесь: https://www.php.net/manual/en/language.exceptions.php хотя объяснение не очень подробное.

Немного подробнее

Один из примеров, который приходит мне на ум, - это если вы имеете дело с потоками ввода/вывода или чем-то подобным, что должно быть закрыто после использования, чтобы избежать утечки памяти. Чтобы использовать ваш пример:

try {
  memoryUser.startReading(someFileOrSomething);
}
catch (CustomException $customException) {
  // handle custom error
}
catch (Exception $exception) {
  // handle the error
  invisibleBug.whoops(); // i.e. something goes wrong in this block
}

memoryUser.Close(); // because something went wrong in the catch block,
                    // this never runs, which, in this case, causes a memory leak

В этом случае перенос memoryUser.Close(); в блок finally обеспечит выполнение этой строки до взрыва остальной части программы, предотвращая утечку памяти даже в случае другого катастрофического сбоя.

TL; DR

Поэтому большую часть времени люди помещают туда блок finally, чтобы обеспечить выполнение важных строк, даже если они что-то пропустили в блоках перехвата. Это то, как я всегда видел это.

Надеюсь, это поможет :)

Ответ 2

Что особенного в блоке finally {}, так это то, что он всегда будет работать в конце блока try {}.

  • Он запустится, если код в блоке try {} завершится успешно.

  • Он запустится, если код в блоке try {} сгенерирует исключение, которое было перехвачено catch {}. (finally {} работает после catch {}.)

  • Он будет работать, если код в блоке try {} выдает исключение, которое не было обработано ни одним блоком catch {}, или если его вообще не было. (Блок finally {} выполняется до того, как исключение передается вызывающей стороне.)

  • Он будет работать, если код в блоке try {} выдает исключение, а код в catch {} выдает другое исключение (или сбрасывает то же исключение).

  • Он даже запустится, если код в блоке try {} или в блоке catch {} использует return. (Как и в случае с неперехваченным исключением, finally {} запускается до фактического возврата из функции.) Блок finally {} может даже использовать сам return, и его возвращаемое значение переопределит значение, которое другой блок попытался вернуть!

(Существует один крайний случай, когда блок finally {} не будет запущен, и это, если весь процесс будет уничтожен, например, в результате уничтожения или вызова exit() или die().)