День семьсот пятнадцатый. #TypesAndLanguages
Довольно интересной показалась серия статей Types and Programming Languages от Adam Furmanek. Интересные наблюдения и тонкости языков программирования. Сегодня первая часть.
1. Не используйте return в блоке finally
Многие языки предоставляют конструкцию обработки исключений, обычно в виде блоков
Некоторые языки поддерживают дополнительный блок
Некоторые языки позволяют возвращать результат из блока
Результат кажется простым и логичным. Но что произойдет, если вы выбросите исключение, а затем используете return? Например, в Java:
Источник: https://blog.adamfurmanek.pl/2021/01/09/types-and-programming-languages-part-1/
Довольно интересной показалась серия статей Types and Programming Languages от Adam Furmanek. Интересные наблюдения и тонкости языков программирования. Сегодня первая часть.
1. Не используйте return в блоке finally
Многие языки предоставляют конструкцию обработки исключений, обычно в виде блоков
try и catch, хотя детали реализации различаются. Программисты обычно предполагают, что эти конструкции работают одинаково для разных языков. К сожалению, это неправда, и нюансы часто бывают сложны для понимания, когда дело доходит до крайних случаев.Некоторые языки поддерживают дополнительный блок
finally, который должен выполняться «несмотря ни на что» - независимо от того, было сгенерировано исключение или нет. Это, правда, не совсем так: есть много ситуаций, когда этот блок не может быть вызван, например, аварийное завершение приложения, ошибки нарушения доступа и т. д. Но сейчас не об этом.Некоторые языки позволяют возвращать результат из блока
finally. В следующем примере на Java последний return «побеждает» другие:public static void main (String[] args)Результат - 42, потому что это последнее возвращаемое значение. Такое же поведение можно видеть в Python, JS, возможно, и на других платформах. Примечательным исключением здесь является C#, который не позволяет использовать return в блоке finally, просто чтобы избежать этой путаницы.
throws java.lang.Exception {
System.out.println(foo());
}
public static int foo(){
try{
return 5;
}finally{
return 42;
}
}
Результат кажется простым и логичным. Но что произойдет, если вы выбросите исключение, а затем используете return? Например, в Java:
public static void main (String[] args)Результат – всё равно 42. Исключение «проглатывается». То же самое в JS:
throws java.lang.Exception {
System.out.println(foo());
}
public static int foo() {
try {
throw new RuntimeException(
"Это сообщение пропадает");
}finally{
return 42;
}
}
function foo(){
try{
throw "Это сообщение пропадает";
}finally{
return 42;
}
}
console.log(foo());
Python:def foo():В общем случае, никогда не используйте
try:
raise Exception("Это сообщение пропадает")
finally:
return 42
print(foo())
return в блоке finally. Это ломает механизм обработки исключений.Источник: https://blog.adamfurmanek.pl/2021/01/09/types-and-programming-languages-part-1/
День семьсот двадцать первый. #TypesAndLanguages
2. Исключение при Обработке Исключения
В прошлый раз мы рассмотрели, что не стоит использовать
Результат различается в разных языках. Например, C# теряет исключение, что указано в спецификации:
Если блок finally выбрасывает другое исключение, обработка текущего исключения прекращается.
Стоит отметить, что некоторые языки предоставляют поле в классе исключения, которое должно хранить предыдущее. Но, если внутреннее исключение не устанавливается автоматически платформой, то это поле нам никак не поможет.
Это важно при работе с ресурсами. Некоторые языки предоставляют конструкцию типа
Источники:
- https://blog.adamfurmanek.pl/2021/01/16/types-and-programming-languages-part-2/
- https://blog.adamfurmanek.pl/2020/07/25/net-inside-out-part-21/
2. Исключение при Обработке Исключения
В прошлый раз мы рассмотрели, что не стоит использовать
return в блоке finally. Сегодня исследуем аналогичный случай исключения при обработке исключения:try{
try{
throw new Exception("Exception 1");
}finally{
throw new Exception("Exception 2");
}
}catch(Exception e){
Console.WriteLine(e);
}
Что будет выведено? Этот вопрос с подвохом. Во-первых, есть два исключения, и мы знаем, что некоторые языки (включая платформу .NET) реализуют двухпроходную систему обработки исключений. Первый проход по стеку ищет обработчик, способный обработать исключение, затем второй проход раскручивает стек и выполняет все блоки finally. Но что, если мы выбросим исключение во втором проходе?Результат различается в разных языках. Например, C# теряет исключение, что указано в спецификации:
Если блок finally выбрасывает другое исключение, обработка текущего исключения прекращается.
Стоит отметить, что некоторые языки предоставляют поле в классе исключения, которое должно хранить предыдущее. Но, если внутреннее исключение не устанавливается автоматически платформой, то это поле нам никак не поможет.
Это важно при работе с ресурсами. Некоторые языки предоставляют конструкцию типа
using в C#:using(var resource = new Resource()){
//…
}
Концептуально это аналогично следующему коду:var resource = new Resource();Кажется довольно просто. Но что если
try{
//…
} finally{
if(resource != null) resource.Dispose();
}
Dispose выбросит исключение: public class Resource : IDisposable {
public void Dispose(){
Console.WriteLine("Disposing");
throw new Exception("Disposing failed");
}
}
Тогда следующий код:try{
using(var resource = new Resource()){
Console.WriteLine("Using");
throw new Exception("Using failed");
}
}catch(Exception e){
Console.WriteLine("Exception: " + e);
}
выведет UsingМы теряем исключение "Using failed" из-за нового исключения "Disposing failed" в блоке finally. Кстати, в Java это реализовано правильно, и сохраняет оба исключения.
Disposing
Exception: System.Exception: Disposing failed
at Resource.Dispose()
at Program.Main()
Источники:
- https://blog.adamfurmanek.pl/2021/01/16/types-and-programming-languages-part-2/
- https://blog.adamfurmanek.pl/2020/07/25/net-inside-out-part-21/
День семьсот тридцать шестой. #TypesAndLanguages
3. Finally при Прекращении Работы
Рассмотрим следующий код:
Внутри обработанного исключения гарантируется выполнение связанного блока
Поэтому блок
Дальше больше, потому что всё может зависеть от типа исключения. Например, в .NET есть атрибут HandleProcessCorruptedStateExceptions:
Исключения повреждённого состояния процесса - это исключения, которые указывают, что состояние процесса было повреждено. Дальнейшее выполнение приложения в этом состоянии не рекомендуется.
По умолчанию среда CLR не передаёт эти исключения управляемому коду, и блоки
Таким образом, ваше приложение может продолжить работу, но не все блоки
Аналогичный вопрос возникает, когда вместо создания исключения вы выходите из приложения, вызывая
Зачем это нужно? Потому что мы обычно освобождаем ресурсы в блоке finally. Если эти ресурсы являются локальными для процесса, тогда это не имеет большого значения, но как только вы начнете использовать межпроцессные вещи (например, общесистемные мьютексы), важно освободить их, иначе другой пользователь может не знать, повреждено защищённое состояние или нет.
Это уже не говоря о том, что необработанное исключение может (.NET) или не может (JVM) привести к остановке всего приложения.
Вывод
Всегда помещайте в поток исполнения глобальный обработчик
Источник: https://blog.adamfurmanek.pl/2021/01/23/types-and-programming-languages-part-3/
3. Finally при Прекращении Работы
Рассмотрим следующий код:
try {
throw new Exception("Exception 1");
}finally{
// очистка
}
Допустим, в этом потоке исполнения нет блока catch. Что произойдёт? Это зависит от платформы. Например, в документации C# по finally говорится:Внутри обработанного исключения гарантируется выполнение связанного блока
finally. Однако если исключение не обработано, то выполнение блока finally зависит от того, как запускается операция развертывания исключения. Это, в свою очередь, зависит от способа настройки компьютера.Поэтому блок
finally может и не выполниться. В то же время, согласно этому документу, JVM гарантирует исполнение блока finally.Дальше больше, потому что всё может зависеть от типа исключения. Например, в .NET есть атрибут HandleProcessCorruptedStateExceptions:
Исключения повреждённого состояния процесса - это исключения, которые указывают, что состояние процесса было повреждено. Дальнейшее выполнение приложения в этом состоянии не рекомендуется.
По умолчанию среда CLR не передаёт эти исключения управляемому коду, и блоки
try-catch (или другие способы обработки исключений) для них не вызываются. Если вы абсолютно уверены, что хотите самостоятельно обрабатывать такие исключения, вы должны применить атрибут HandleProcessCorruptedStateExceptions к методу, в котором вы хотите выполнять блоки обработки таких исключений. CLR предоставляет исключения повреждённого состояния процесса в применимые блоки обработки исключений только в методах, которые имеют атрибуты HandleProcessCorruptedStateExceptions или SecurityCritical.Таким образом, ваше приложение может продолжить работу, но не все блоки
finally могут быть выполнены.Аналогичный вопрос возникает, когда вместо создания исключения вы выходите из приложения, вызывая
Application.Exit(). Будет ли исполнен блок finally?Зачем это нужно? Потому что мы обычно освобождаем ресурсы в блоке finally. Если эти ресурсы являются локальными для процесса, тогда это не имеет большого значения, но как только вы начнете использовать межпроцессные вещи (например, общесистемные мьютексы), важно освободить их, иначе другой пользователь может не знать, повреждено защищённое состояние или нет.
Это уже не говоря о том, что необработанное исключение может (.NET) или не может (JVM) привести к остановке всего приложения.
Вывод
Всегда помещайте в поток исполнения глобальный обработчик
try-catch.Источник: https://blog.adamfurmanek.pl/2021/01/23/types-and-programming-languages-part-3/