Статьи

Обработка исключений

В этом посте я поделюсь с вами, как делается обработка ошибок и какие у нас есть варианты. Обработка ошибок — сложная тема 🙂

Я добавлю некоторый контекст из Википедии о том, что такое обработка исключений, прежде чем идти по кроличьей норе обработки исключений

Обработка исключений — это процесс реагирования на возникновение во время вычислений исключений — аномальных или исключительных условий, требующих специальной обработки, — часто нарушающих нормальный ход выполнения программы . Это обеспечивается специализированными конструкциями языка программирования , механизмами компьютерного оборудования , такими как прерывания или
средства IPC операционной системы, такие как сигналы .

Как правило, исключение нарушает нормальный поток выполнения и выполняет предварительно зарегистрированный обработчик исключений . Детали того, как это делается, зависят от того, является ли это аппаратным или программным исключением, и как реализовано программное исключение. Некоторые исключения, особенно аппаратные, могут обрабатываться настолько изящно, что выполнение может возобновиться с того места, где оно было прервано.

Источник — https://en.wikipedia.org/wiki/Exception_handling

Мало что выделяется: « нарушает нормальный ход выполнения программы », « предварительно зарегистрированный обработчик исключений», аппаратное или программное исключение.

Это объясняет, что такое обработка ошибок, поэтому я не буду тратить на это время.

Следует упомянуть одну интересную вещь: существуют два типа исключений (аппаратное и программное обеспечение), друзья по аппаратному обеспечению справились с этим очень изящно, и разработчик программного обеспечения должен был это сделать.

Программное обеспечение — это сложно, а слишком много языков программирования делает его еще сложнее.

Я хочу, чтобы вы обратились к сообщению о простом тестировании, которое может предотвратить большинство, в котором я пытаюсь объяснить побочный эффект неправильной обработки ошибок, и это чисто как результат обработки исключений.

C путь

Я уверен, что вы видели это и думали, что «это правильный путь?» ,

Фрагмент кода обработки ошибок C

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
          int main () 
FILE * fp; 
fp = fopen ("somejunkfile.txt", "rb"); 
  
if (fp == NULL) 
printf("errno: %d76485//"////, errno); 
printf("Error opening the file: %s76485//"////, strerror//(errno)); 
exit(EXIT_FAILURE)
else
fclose (fp); 
return 0
}

Этот подход имеет несколько проблем

— Вы должны проверить на наличие ошибок после вызова каждой функции, которая может дать сбой.

— Отсутствие защиты от компилятора при форсировании / указании того, что ошибка может быть выдана

— Обработка ошибок совершенно необязательна

Java Way

Затем пришла java и пришла с настройом, позволившим мне исправить всю обработку ошибок, и они изобрели проверенное / непроверенное исключение.

Посмотрите на фрагмент кода

1
2
3
4
5
6
7
          public void close() {
try {
this.writer.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

Этот подход имеет все больше проблем

— Код заполнен многословием кода обработки ошибок

— Компилятор заставляет вас обрабатывать проверенное исключение неправильно (т.е. просто регистрировать его или игнорировать)

— Ничего осмысленного не делается, кроме как что-то записать в журнал и обернуть в RuntimeExcetion, чтобы получить пропущенный компилятор.

— Оборачивание ухудшает ситуацию, потому что вы начинаете терять контекст того, что вызвало ошибку

Способ функционального программирования

Этот мир должен делать лучше, чем «императивный» мир и что они сделали? Изобретенные монады.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
          def main(args: Array[String]): Unit = {
  
val res = divide(10, 0)
res match {
case Left(value) => println(s"Result is ${value}")
case Right(e) => println(s"Calculation failed reason ${e}")
}
  
val result = trydivide(10, 0)
result match {
case Success(r) => println(s"Result ${r}")
case Failure(error) => println(s"Failed to calculate reason : ${error.getMessage}")
}
  
  
}

Что следует учитывать при использовании этого подхода

— Вы должны выучить причудливый технический жаргон теории категорий или монад

— Теперь дает 2 значения, и вы должны написать немного меньше подробного кода, чтобы обрабатывать оба пути

— Проблемы с производительностью из-за дополнительной оборачиваемости стоимости, и когда у вас их миллионы, это очень сильно бьет вас

— Нет безопасности во время компиляции, у вызывающей стороны есть возможность обойти это путем непосредственного получения значения

— и я думаю, что это была попытка исправить значение Optional / MayBe, в котором вы не знаете, почему значение не доступно.

— Stacktrace ушел, и в некоторых случаях он полезен, особенно для построения системы, которая вызывает сторонние библиотеки

Go Lang Way

GoLang хотел добиться большего успеха, чем C / Java, и черпал вдохновение из языка Elm и придумал подход с делегированием или Killer (т.е. Panic)

1
2
3
4
          listener, listenError := net.ListenTCP("tcp", addr)
if listenError != nil {
return nil, fmt.Errorf("Listen: %s", listenError)
}

Это интересный подход, но

— с ошибкой возврата в самом вызове функции, вызывающая сторона должна добавить код обработки ошибок

— Трассировка потеряна, поэтому вы должны быть очень осторожны при добавлении всего контекста к сообщению, чтобы было возможно восстановление.

— Паника не подходит для библиотеки или фреймворка, потому что вы не можете убить процесс, это должно быть ответственностью клиента, чтобы решить, что делать.

JavaScript / Python путь

Я оставлю это сейчас.

Нет ясного победителя, в каком варианте лучше, и каждый язык делает некоторый компромисс. Мы не исключение, как java и как Go Lang, это 2 маятник.

Что может быть хорошо, так это возможность вызывающего абонента решить, какой подход использовать, это может быть Java Style или Go Lang.

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

Таким образом, код в блоке catch много говорит о том, чего хочет клиент, и это должно решить, какой шаблон обработки ошибок следует использовать.
Я думаю, что это больше об образовании о том, что правильно в контексте и использовании шаблона.

Рад узнать, что вы думаете об обработке ошибок и как это должно быть сделано.

Смотрите оригинальную статью здесь: обработка исключений

Мнения авторов .NET Code Geeks — их собственные.