Pull to refresh

Оптимизация ошибок?!

Reading time 4 min
Views 4.5K

Введение


Меня до глубины души задело заявление моего коллеги, что использовать исключения — это неправильно. А далее последовала череда объяснений: это медленно, это некрасиво, это неэффективно, это неудобно.

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

Из заявления следует, что во время разработки идет оптимизация ошибок, или в более общем смысле преждевременная оптимизация. Многие знают, что преждевременная оптимизация вредна. Но оптимизация ошибок ведет к возникновению новых ошибок, усложняет код и дестабилизирует весь код.

Простые примеры


Рассмотрим два простых примера, на основе которых можно описать большинство происходящих ошибок:

Point readPoint() {
  ...
}

Points readPoints() {
  ...
}

Придерживаясь правила не использовать исключения в своем коде, придется возвращать и обрабатывать некий результат:

Point point = readPoint();
if (point != null) {
  ...
}

Points points = readPoints();
if (points != null) {
  ...  
}

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

Если функция возвращает null, то она не выполняет свои обязанности. Из сигнатуры функции понятно, что она возвращает точку или набор точек, но null — это не точка, поэтому функция нас обманывает.

В некоторых ситуациях разумно возвращать специальное значение. Точка с нулевыми или отрицательными координатами мало подходит для таких задач. Система может быть расширена, и тогда точка попадет во множество нормальных точек, код начнет вести себя непредсказуемо. Можно придумать специальную точку.

А вот вернуть пустой набор точек во второй функции вполне разумно в некоторых случаях:

Points points = readPoints();
for (Point point : points) {
  ...
}

Цикл просто не выполнит ни одной итерации, функция обладает ожидаемым поведением.

Использование исключений


Теперь посмотрим, как выглядят эти же примеры с использованием исключений:

Point readPoint() throws PointNotFoundException {
  ...
}

Points readPoints() throws CouldNotReadPointsException {
  ...
}

Были добавлены два новых типа исключений:
  • PointNotFoundException — возникает, когда не удается получить точку;
  • CouldNotReadPointsException — возникает, когда не удается получить набор точек.

Тогда новые функции будут использоваться так:

try {
  Point point = readPoint();
  ...
} catch(PointNotFoudException ex) {
  ...
}

try {
  Points points = readPoints();
  for (Point point : points) {
    ...
  }
} catch(CouldNotReadPointsException ex) {
  ...
}

Мы спокойно реализуем бизнес-логику, а если возникает ошибка, то обрабатываем ее в отдельном блоке. Код становится более ясным и понятным.

Производительность


Теперь поговорим о производительности. Механизм исключений — это всегда дополнительные затраты производительности. Затраты уходят на его поддержку. Но эти затраты становятся актуальными только при возникновении ошибок. Что код делает 99,9% времени? Работает, а не обрабатывает ошибки.

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

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

while (!queue.isEmpty()) {
  Point point = queue.readPoint();
  if (point != null) {
    ...
  }
}

В этом примере проверка на null будет выполнятся всегда, при каждой итерации.

Пример с использованием исключений:

try {
  while (!queue.isEmpty()) {
    Point point = queue.readPoint();
    ...
  }
} catch(PointNotFoudException ex) {
  throw new QueueReadingException(ex);
}

Можно упростить код, разделив бизнес-логику и код обработки ошибок:

try {
  while (!queue.isEmpty()) {
    processNextPoint();
  }
} catch(PointNotFoudException ex) {
  throw new QueueReadingException(ex);
}

void processNextPoint() throws PointNotFoudException {
  Point point = queue.readPoint();
  ...
}

Сколько раз в процессе штатной работы будет инициировано исключение QueueReadingException? В коде без ошибок — ни разу! Где же тут затраты?

Поток данных из сети


Пример немного утрирован. Помните, что инициация исключения влечет за собой просмотр стека, всех вызовов, поиск строк, из которых был вызван данный код и создание объекта исключения(о-очень долго).

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

Например, затраты на одно сравнение равны единице производительности. Инициация исключения 5 единиц. Пусть очередь содержит 100 точек, с 10% вероятностью возникновения ошибок чтения. Получается, на сравнениях теряется 100 единиц производительности, на инициации исключений в худшем случае 50 единиц. Если же увеличивать количество точек в очереди, то разница будет только возрастать(для тысячи точек она равна уже 900 единиц).

Выводы


Исключения стоит использовать, использовать с умом в самых необходимых местах. Бизнес-логика будет разделена от кода обработки ошибок, сопровождение упростится. Код станет более элегантным, и на производительности это сильно не скажется. Стоит отметить, что исключения не должны использоваться в нормальном ходе исполнения программы. Исключение, это всегда ошибка, которая может быть достаточно серьезной и ее невозможно решить на месте.

P. S. Исправил названия методов с get- на read. Новые имена более четко выражают суть статьи. Логично же предположить, что для геттера инициировать исключение это полная глупость.

Исключение — это ошибка времени выполнения(источник). Обычные ошибки в программах, а тем более результаты работы некоторых алгоритмов возвращать с помощью исключений неверно. Об этом несколько раз упоминается в статье.
Tags:
Hubs:
+34
Comments 190
Comments Comments 190

Articles