Существуют различные способы обработки ошибок в языках программирования: Исключения используются очень широко, с другой стороны о них часто говорят, что они медленные. Но и противники функционального подхода часто апеллируют к производительности. Последнее время я работаю со Scala, где в равной мере я могу использовать как исключения так и различные типы данных для обработки ошибок, поэтому интересно какой из подходов будет удобнее и быстрее. Сразу отбросим использование кодов и флагов, так как этот подход не принят в JVM языках и по моему мнению слишком подвержен ошибкам (прошу прощения за каламбур). Поэтому будем сравнивать исключения и разные виды АТД. Кроме того АТД можно рассматривать как использование кодов ошибок в функциональном стиле. UPDATE: к сравнению добавлены исключения без стек-трейсов Для тех, кто не слишком знаком с АТД (ADT) — алгебраический тип состоит из нескольких возможных значений, каждое из которых может быть составным значением (структурой, записью). Примером может служить тип Option[T] = Some(value: T) | None, который используется вместо null-ов: значением данного типа может быть либо Some(t) если значение есть, либо None если его нет. Другим примером может быть Try[T] = Success(value: T) | Failure(exception: Throwable), который описывает результат вычисления, которое могло завершиться успешно либо с ошибкой. Итак наши конкурсанты: NOTE по-сути сравниваются исключения со стек-трейсом, без и АТД, но выбрано несколько типов, так как в Scala нет единого подхода и интересно сравнить несколько. Кроме исключений тут используются строки для описания ошибок, но с тем же успехом в реальной ситуации использовались бы различные классы (Either[Failure, T]). Для тестирование обработки ошибок возьмем проблему парсинга и валидации данных: т.е. имея сырые данные Map[String, String] нужно получить Person или ошибку если данные не валидны. Решение в лоб с использованием исключений (тут и далее буду приводить только функцию person, с полным кодом ознакомится можно на github):
ThrowParser.scala тут string, integer и boolean валидируют наличие и формат простых типов и производят преобразование.
В целом довольно просто и понятно. Код такой же, как и в предыдущем случае, но используются исключения без стек-трейса где можно: ThrowNSTParser.scala Решение перехватывает исключения раньше и позволяет комбинировать результаты через for (не путать с циклами в других языках):
TryParser.scala немного более непривычно для неокрепшего глаза, но за счет использования for в целом очень похоже на версию с исключениями, кроме того валидация наличия поля и парсинг нужного типа происходят отдельно (flatMap тут можно читать как and then) Тут тип Either спрятан за алиасом Result так как тип ошибки фиксирован:
EitherParser.scala Поскольку стандартный Either как и Try формирует монаду в Scala то код вышел абсолютно такой же, отличие тут в том, что в качестве ошибки тут фигурирует строка и исключения используются минимально (только для обработки ошибки при парсинге числа) Тут используется библиотека Cats для того чтобы получить в случае ошибки не первую произошедшую, но как можно больше (например если несколько полей были не валидными, то результат будет содержать ошибки парсинга всех этих полей)
ValidatedParser.scala этот код уже менее похож на исходную версию с исключениями, но зато проверка дополнительных ограничений не оторвана от парсинга полей и мы все-таки получаем несколько ошибок вместо одной, это того стоит! Для тестирование генерировался набор данных с различным процентом ошибок и парсился каждым из способов. Результат на всех процентах ошибок:
 Более подробно на низком проценте ошибок (время тут другое так как использовалась большая выборка):
 Если же какая-то часть ошибок все-же является исключением со стек-трейсом (в нашем случае ошибка парсинга числа будет исключением, которое мы не контролируем) то конечно производительность "быстрых" способов обработки ошибок будет значительно ухудшаться. Особенно страдает Validated, так как он собирает все ошибки и в результате получает медленное исключение больше других:
 Как показал эксперимент исключения со стек-трейсами действительно очень медленные (на 100% ошибок разница между Throw и Either более чем в 50 раз!), а когда исключений практически нет использование АТД имеет свою цену. Однако использование исключений без стек-трейсов так же быстро (а при низком проценте ошибок быстрее) как и АДТ, однако если такие исключения выйдут за пределы той же валидации отследить их источник будет не легко. Итого, если вероятность исключения более 1% то быстрее всего работают исключения без стек-трейсов, Validated или обычный Either почти так же быстры. При большом количестве ошибок Either может быть немного быстрее Validated только за счет семантики fail-fast. Использование АТД для обработки ошибок дает еще одно преимущество перед исключениями: возможность ошибки зашита в сам тип и ее сложнее упустить, как и при использовании Option вместо null'ов. Разработчик