Многие современные языки предоставляют богатые функции обработки исключений , но язык программирования Apple Swift не предоставляет механизм обработки исключений .
Несмотря на то, что я погружен в исключения, мне трудно понять, что это значит. Swift имеет утверждения и, конечно, возвращает значения; но мне сложно представить, как мой образ мышления, основанный на исключениях, отображает мир без исключений (и, в этом отношении, почему такой мир желателен ). Есть ли вещи, которые я не могу сделать на языке вроде Swift, которые я мог бы делать с исключениями? Получу ли я что-то, потеряв исключения?
Как, например, я мог бы лучше выразить что-то вроде
try:
operation_that_can_throw_ioerror()
except IOError:
handle_the_exception_somehow()
else:
# we don't want to catch the IOError if it's raised
another_operation_that_can_throw_ioerror()
finally:
something_we_always_need_to_do()
в языке (например, Swift), в котором отсутствует обработка исключений?
panic
что не совсем то же самое. В дополнение к тому, что там сказано, исключение - это не более чем сложный (но удобный) способ выполненияGOTO
, хотя никто не называет это так по понятным причинам.Ответы:
Во встроенном программировании исключения традиционно не допускались, потому что накладные расходы на разматывание стека, которые вам приходилось делать, считались недопустимой изменчивостью при попытке поддерживать производительность в реальном времени. Хотя технически смартфоны можно считать платформами реального времени, сейчас они достаточно мощные, где старые ограничения встроенных систем больше не применяются. Я просто поднимаю это ради тщательности.
Исключения часто поддерживаются в функциональных языках программирования, но используются настолько редко, что их вполне может не быть. Одна из причин - ленивая оценка, которая иногда выполняется даже в тех языках, которые по умолчанию не ленивы. Наличие функции, которая выполняется со стеком, отличным от места, в котором она была поставлена в очередь, затрудняет определение места размещения обработчика исключений.
Другая причина заключается в том, что функции первого класса допускают такие конструкции, как опции и фьючерсы, которые дают вам синтаксические преимущества исключений с большей гибкостью. Другими словами, остальная часть языка достаточно выразительна, чтобы исключения ничего не покупали.
Я не знаком со Swift, но то, что я читал об обработке ошибок, показывает, что они предназначены для обработки ошибок в соответствии с более функциональными шаблонами. Я видел примеры кода с
success
иfailure
блоки , которые очень похожи на фьючерсы.Вот пример с использованием
Future
от этого Scala учебника :Вы можете видеть, что он имеет примерно ту же структуру, что и ваш пример с использованием исключений.
future
Блок подобноtry
.onFailure
Блок , как обработчик исключений. В Scala, как и в большинстве функциональных языков,Future
реализован полностью с использованием самого языка. Он не требует специального синтаксиса, как это делают исключения. Это означает, что вы можете определить свои собственные аналогичные конструкции. Может быть, добавитьtimeout
блок, например, или функциональность регистрации.Кроме того, вы можете передать будущее, вернуть его из функции, сохранить его в структуре данных или что-то еще. Это первоклассная ценность. Вы не ограничены как исключения, которые должны распространяться прямо вверх по стеку.
Опции решают проблему обработки ошибок немного по-другому, что лучше работает в некоторых случаях. Вы не застряли только с одним методом.
Это те вещи, которые вы «получаете, теряя исключения».
источник
Future
что, по сути, это способ проверки значения, возвращаемого функцией, не останавливаясь для его ожидания. Как и Swift, он основан на возвращаемом значении, но в отличие от Swift, ответ на возвращаемое значение может произойти позже (немного похоже на исключения). Правильно?Future
правильно, но я думаю, что вы, возможно, неправильно характеризуете Свифта. Посмотрите первую часть этого ответа stackoverflow , например.Either
будет лучшим примером ИМХОИсключения могут усложнить анализ кода. Хотя они не так сильны, как gotos, они могут вызвать много таких же проблем из-за их нелокальной природы. Например, допустим, у вас есть фрагмент императивного кода, подобный этому:
Вы не можете сразу определить, может ли какая-либо из этих процедур вызвать исключение. Вы должны прочитать документацию каждой из этих процедур, чтобы понять это. (Некоторые языки делают это немного проще, дополняя сигнатуру типа этой информацией.) Приведенный выше код будет прекрасно компилироваться независимо от того, будет ли выброшена какая-либо из процедур, что позволяет легко забыть обработать исключение.
Кроме того, даже если цель состоит в том, чтобы передать исключение обратно вызывающей стороне, часто необходимо добавить дополнительный код, чтобы предотвратить оставление вещей в несогласованном состоянии (например, если ваша кофеварка сломалась, вам все равно нужно навести порядок и вернуть кружка!). Таким образом, во многих случаях код, использующий исключения, будет выглядеть так же сложно, как и код, который этого не делает из-за необходимости дополнительной очистки.
Исключения можно эмулировать с помощью достаточно мощной системы типов. Многие из языков, которые избегают исключений, используют возвращаемые значения для того же поведения. Это похоже на то, как это делается в C, но современные системы типов обычно делают его более элегантным, а также сложнее забыть обработать условие ошибки. Они могут также обеспечить синтаксический сахар, чтобы сделать вещи менее громоздкими, иногда почти такими же чистыми, как это было бы с исключениями.
В частности, встраивание обработки ошибок в систему типов вместо реализации в качестве отдельной функции позволяет использовать «исключения» для других вещей, которые даже не связаны с ошибками. (Хорошо известно, что обработка исключений на самом деле является свойством монад.)
источник
goto
: Thegoto
ограничен одной функцией, которая является довольно небольшим диапазоном, при условии, что функция действительно мала, исключение действует скорее как некоторый крестgoto
иcome from
(см. en.wikipedia.org/wiki/INTERCAL ;-)). Он может в значительной степени соединить любые две части кода, возможно, пропуская код некоторых третьих функций. Единственное, что он не может сделать, чтоgoto
может, это вернуться назад.Здесь есть несколько хороших ответов, но я думаю, что одна важная причина не была подчеркнута достаточно: когда возникают исключения, объекты могут быть оставлены в недопустимых состояниях. Если вы можете «перехватить» исключение, то ваш код обработчика исключений сможет получить доступ к этим недопустимым объектам и работать с ними. Это будет ужасно неправильно, если код для этих объектов не будет написан идеально, что очень и очень трудно сделать.
Например, представьте себе реализацию Vector. Если кто-то создает экземпляр Vector с набором объектов, но во время инициализации возникает исключение (например, при копировании ваших объектов во вновь выделенную память), очень трудно правильно кодировать реализацию Vector таким образом, чтобы память просочилась. Эта короткая статья Stroustroup охватывает пример вектора .
И это всего лишь верхушка айсберга. Что, если, например, вы скопировали некоторые элементы, но не все элементы? Для правильной реализации контейнера, такого как Vector, вам практически необходимо сделать каждое действие, которое вы выполняете, обратимым, поэтому вся операция является атомарной (например, транзакция базы данных). Это сложно, и большинство приложений ошибаются. И даже если это сделано правильно, это значительно усложняет процесс реализации контейнера.
Поэтому некоторые современные языки решили, что это того не стоит. Например, у Rust есть исключения, но они не могут быть «перехвачены», поэтому у кода нет возможности взаимодействовать с объектами в недопустимом состоянии.
источник
Изначально я был удивлен языком Rust: он не поддерживает исключения catch. Вы можете генерировать исключения, но только среда выполнения может их перехватить, когда задача (поток обработки, но не всегда отдельный поток ОС) умирает; если вы начинаете задание самостоятельно, то можете спросить, нормально ли оно вышло или выполнило его
fail!()
.Как таковой, это не идиоматично, чтобы
fail
очень часто. Несколько случаев, когда это происходит, например, в тестовом жгуте (который не знает, на что похож пользовательский код), в качестве верхнего уровня компилятора (вместо этого используется большая часть компиляторов) или при вызове обратного вызова. на ввод пользователя.Вместо этого, общая идиома использовать в
Result
шаблон явно пропустить ошибки , которые должны быть обработаны. Это значительно упрощается с помощьюtry!
макроса , который можно обернуть вокруг любого выражения, которое выдает Result и дает успешное плечо, если оно есть, или иным образом возвращается из функции рано.источник
assert
, но нетcatch
?На мой взгляд, исключения являются важным инструментом для обнаружения ошибок кода во время выполнения. Как в тестах, так и в производстве. Сделайте их сообщения достаточно многословными, чтобы в сочетании с трассировкой стека вы могли выяснить, что произошло из журнала.
Исключения - это в основном инструмент разработки и способ получения разумных отчетов об ошибках в производственной среде в непредвиденных случаях.
Помимо разделения интересов (удачный путь только с ожидаемыми ошибками и провал до достижения какого-то общего обработчика непредвиденных ошибок), являющегося хорошей вещью, делающего ваш код более читабельным и обслуживаемым, фактически невозможно подготовить ваш код для всех возможных непредвиденные случаи, даже вздувая его с кодом обработки ошибок, чтобы завершить нечитаемость.
Это на самом деле означает «неожиданный».
Кстати, что ожидается, а что нет, это решение, которое может быть принято только на сайте вызова. Вот почему проверенные исключения в Java не сработали - решение принимается во время разработки API, когда совсем не ясно, что ожидается или неожиданно.
Простой пример: API хеш-карты может иметь два метода:
а также
первое выдает исключение, если не найдено, последнее дает необязательное значение. В некоторых случаях последний имеет больше смысла, но в других ваш код просто должен ожидать, что для данного ключа будет значение, поэтому если его нет, то это ошибка, которую этот код не может исправить, так как основной предположение не удалось. В этом случае на самом деле желаемое поведение выпадать из пути кода и переходить к некоторому универсальному обработчику в случае сбоя вызова.
Кодекс никогда не должен пытаться справиться с ошибочными базовыми предположениями.
Конечно, кроме проверки их и создания хорошо читаемых исключений.
Бросать исключения - не зло, но ловить их можно. Не пытайтесь исправить неожиданные ошибки. Поймайте исключения в нескольких местах, где вы хотите продолжить цикл или операцию, зарегистрируйте их, возможно, сообщите о неизвестной ошибке, и все.
Поймать блоки повсюду - очень плохая идея.
Создайте свои API-интерфейсы таким образом, чтобы с легкостью выразить свое намерение, т.е. заявить, ожидаете ли вы определенного случая, например, ключ не найден или нет. Пользователи вашего API могут затем выбирать вызов только для действительно неожиданных случаев.
Я предполагаю, что причина, по которой люди возмущаются исключениями и зашли слишком далеко, опуская этот критически важный инструмент для автоматизации обработки ошибок и лучшего отделения проблем от новых языков, - это плохой опыт.
Это и некоторое недопонимание того, для чего они на самом деле хороши.
Имитация их путем выполнения ВСЕХ через монадическое связывание делает ваш код менее читаемым и обслуживаемым, и вы в конечном итоге не получаете трассировки стека, что делает этот подход НАМНОГО хуже.
Обработка ошибок в функциональном стиле отлично подходит для ожидаемых ошибок.
Пусть обработка исключений автоматически позаботится обо всем остальном, вот для чего это :)
источник
Свифт использует те же принципы, что и Objective-C, но более последовательно. В Objective-C исключения указывают на ошибки программирования. Они не обрабатываются, кроме как с помощью инструментов отчетности о сбоях. «Обработка исключений» выполняется путем исправления кода. (Есть несколько исключений. Например, в межпроцессном взаимодействии. Но это довольно редко, и многие люди никогда не сталкиваются с этим. А в Objective-C фактически есть try / catch / finally / throw, но вы редко используете их). Swift только что убрал возможность ловить исключения.
Swift имеет функцию, которая выглядит как обработка исключений, но является просто принудительной обработкой ошибок. Исторически Objective-C имел довольно распространенный шаблон обработки ошибок: метод либо возвращал BOOL (YES для успеха), либо ссылку на объект (nil для ошибки, не nil для успеха) и имел параметр «указатель на NSError *» который будет использоваться для хранения ссылки NSError. Swift автоматически преобразует вызовы такого метода в нечто похожее на обработку исключений.
В общем, функции Swift могут легко возвращать альтернативы, например, результат, если функция работала нормально, и ошибку, если она не удалась; это делает обработку ошибок намного проще. Но ответ на первоначальный вопрос: дизайнеры Swift явно чувствовали, что создать безопасный язык и написать успешный код на таком языке легче, если у него нет исключений.
источник
В C вы бы в конечном итоге что-то вроде выше.
Нет, ничего нет Вы просто обрабатываете коды результатов, а не исключения.
Исключения позволяют вам реорганизовать ваш код так, чтобы обработка ошибок была отделена от вашего счастливого кода пути, но это все.
источник
...throw_ioerror()
возвращать ошибки, а не генерировать исключения?Result<T, E>
перечисление, которое может быть либоOk<T>
, либоErr<E>
, сT
тем типа вы хотите , если таковой имеется, иE
быть типом , представляющий ошибку. Сопоставление с образцом и некоторые конкретные методы упрощают обработку как успехов, так и неудач. Короче говоря, не предполагайте, что отсутствие исключений автоматически означает коды ошибок.В дополнение к ответу Чарли:
Эти примеры заявленной обработки исключений, которые вы видите во многих руководствах и книгах, выглядят очень умно только на очень маленьких примерах.
Даже если вы оставите в стороне аргумент о недопустимом состоянии объекта, они всегда вызывают огромную боль при работе с большим приложением.
Например, когда вам приходится иметь дело с IO, используя некоторую криптографию, у вас может быть 20 видов исключений, которые могут быть выброшены из 50 методов. Представьте, сколько кода обработки исключений вам понадобится. Обработка исключений займет в несколько раз больше кода, чем сам код.
В действительности вы знаете, когда исключение не может появиться, и вам просто не нужно писать так много обработки исключений, поэтому вы просто используете некоторые обходные пути, чтобы игнорировать объявленные исключения. В моей практике только около 5% заявленных исключений необходимо обрабатывать в коде, чтобы иметь надежное приложение.
источник