Pull to refresh

Comments 135

UFO just landed and posted this here
а есть только общий и весьма очевидный посыл

Да ну? По-моему посыл как раз использовать исключения только для нештатных ситуаций, а не «смотреть по ситуации».
да, так и есть. Пытался донести мысль, что только нештатные ситуации следует рассматривать как исключения.
Бизнес определяет эволюцию языков. Часто получается так: бизнес-требования противоречат инженерным требованиям. Значит, с точки зрения инженера, языки и экосистемы развиваются по абсурдной траектории, инженер видит отрицательный отбор. Но бизнесу можно всё, что приводит к увеличению нормы прибыли, а значит, можно и исключения, и GOTO, и плевать на идеалы инженеров тоже можно. «MVP» и всё, сиди пиши говнокод молча, а то вспомним про hire & fire.
Исключение — это то, вероятность (возможность) чего исключается системой… это то что в условиях программы произойти не может.

Извините, но это чушь какая-то. Если это не может, то нет смысла и обрабатывать это. Может быть, лучше написать «произойти не должно»?
к сожалению от этого потеряется категоричность утверждения. Ведь пользователь тоже «не должен» пытаться вводить имя там, где у него просят ввести возраст. Но это не повод для порождения исключения, потому как здравый смысл подсказывает, что он может это сделать (ведь ему никто не запретил)
UFO just landed and posted this here
Исключение — это то, вероятность (возможность) чего исключается системой… это то что в условиях программы произойти не может.

А откуда вы взяли это определение?


Все исключения могут быть продиктованы только заданием или здравым смыслом, и ничем или никем иным. Исключение не определяется ни типом данных, ни их источником

Вы так говорите, как будто тип и источник данных не определяются задачей.


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

Такая ситуация, очевидно, возможна. А вот теперь вопрос — зачем обрабатывать ее на каждом уровне кода (ведь "код не должен ни о чем умалчивать")?


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

Что такое "отрицательный результат выполнения функции"?


Почему любое лишнее Исключение является вредным для кода?

Как определить, является ли исключение лишним?

А откуда вы взяли это определение?

По моему мнению такое определение лучше всего описывает саму суть исключений. Источника нет, как и у статьи в целом. Статья основана на опыте и ранее прочитанных и переваренных материалах

Вы так говорите, как будто тип и источник данных не определяются задачей.

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

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

Такая ситуация, очевидно, возможна. А вот теперь вопрос — зачем обрабатывать ее на каждом уровне кода (ведь «код не должен ни о чем умалчивать»)?

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

Что такое «отрицательный результат выполнения функции»?

Это, например, когда функция сохраняя данные в базу проводит их валидацию. Эта функция может вернуть объект, если тот был удачно сохранён или ошибку валидации. Ошибка валидации — отрицательный результат выполнения (объект не сохранён). Встречал случаи, когда эту ошибку передавали в виде исключения, хотя пользователь просто опечатался при вводе e-mail адреса или телефона :)

Как определить, является ли исключение лишним?

В статье есть ответ на этот вопрос
Можете ли вы к каждому исключению дописать «но ведь это невозможно» или «но это же исключено»?
По моему мнению такое определение лучше всего описывает саму суть исключений.

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


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

Как раз здравый смысл подсказывает, что, поскольку программа не контролирует свое окружение, никакую ситуацию нельзя исключать.


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

Это противоречит — вашей же — позиции "ваш код не должен ни о чем умалчивать". Отсутствие явной обработки исключения — это умолчание.


Это, например, когда функция сохраняя данные в базу проводит их валидацию.

И… почему нельзя ошибку валидации представить в виде исключения?


Эта функция может вернуть объект, если тот был удачно сохранён или ошибку валидации.

Или ошибку сохранения в БД. Или ошибку логирования. Или ошибку нехватки памяти. Вы себе представляете контракт такой функции?


Можете ли вы к каждому исключению дописать «но ведь это невозможно» или «но это же исключено»?

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

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

Я рекомендую, а не пытаюсь кого-то заставить… описанный метод (и соответственно определения) я применяю в своей работе и он работает. Потому да, я предлагаю работающую схему (механизм) по которому у меня не возникает вопросов «вернуть ли код ошибки, false или бросить исключение». Задавая себе один простой вопрос я сразу принимаю решение о реакции на провал операции.

Как раз здравый смысл подсказывает, что, поскольку программа не контролирует свое окружение, никакую ситуацию нельзя исключать.

Если Вас поместить в стабильное окружение, где постоянно есть еда, вода и воздух в достаточных количествах, можете ли вы исключить ситуацию, что находясь в этом окружении задохнётесь от нехватки воздуха, умрёте от голода или жажды? Я вот исключил бы. То есть, я бы бросил исключение «наверх», если бы в какой-то момент почувствовал, что умираю от голода!

Это противоречит — вашей же — позиции «ваш код не должен ни о чем умалчивать». Отсутствие явной обработки исключения — это умолчание.

Он не умалчивает. Он всё ещё исключает возможность возникновения такого случая, так же, как это исключал код уровнем ниже. Позиция разработчика при написании такого кода приблизительно такая: «тот, кто вызовет этот код должен позаботиться о доступности папки на запись, её наличии и о работоспособности накопителя». То есть в этом коде всё ещё исключена невозможность записать файл! И если файл не записался, то исключение, которое бросил код уровнем ниже следует пропустить дальше по цепочке!

И… почему нельзя ошибку валидации представить в виде исключения?

Я не говорил что нельзя. Я говорил что в указанном контексте это плохо. Именно по причине того, что Вы написали далее
Эта функция может вернуть объект, если тот был удачно сохранён или ошибку валидации.

Или ошибку сохранения в БД. Или ошибку логирования. Или ошибку нехватки памяти. Вы себе представляете контракт такой функции?

У этой функции по факту есть 3 результата выполнения:
1. Объект удачно сохранён
2. Поля объекта не прошли валидацию (заполнены неверно)
3. Валидация или сохранение объекта невозможны (завершились ошибкой)

Таким образом у функции есть 2 штатных результата: сохранён/не сохранён. И нештатный результат: невозможно сохранить/проверить. Так зачем ШТАТНУЮ ситуацию представлять в виде исключения? Для меня вполне ожидаемо, что пользователь при регистрации допустит опечатку или не обратит внимания на заявленный шаблон заполнения поля.
В то же время я не ожидаю ситуации, что в момент записи в базу оборвётся соединение. А потому да, обрыв соединения — это исключение! Это значит, что произошло что-то такое, чего произойти в условиях моего кода не может! В момент работы было недопустимо изменено окружение, в котором код выполнялся.

Можете ли вы к каждому исключению дописать «но ведь это невозможно» или «но это же исключено»?

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

Не любое… но многие из них! В моём коде иногда исключения присутствуют… очень часто я пропускаю исключения на самый верх и отдаю их пользователю в виде ошибки… вроде «недостаточно прав для сохранения файла, свяжитесь с администратором для решения проблемы»
Но на то они и исключения, что бы их было мало, а не весь код был ими напичкан по 3 штуки на функцию!

Я понимаю, что Вы можете быть со мной не согласны. Но как я писал ранее, это работающая схема, которая имеет полное право на жизнь. Я не пытался кого-то переубедить или что-то доказать. Как я и писал, эта статья — совет новичкам по позиционированию исключений как таковых… чем они являются и когда нужны. А учитывая, что это работает и работает неплохо, значит в согласии других не очень нуждается (грустно конечно, что многие оказались несогласны со мной, но всё же… суть статьи не в переубеждении)
Потому да, я предлагаю работающую схему (механизм) по которому у меня не возникает вопросов «вернуть ли код ошибки, false или бросить исключение».

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


Если Вас поместить в стабильное окружение, где постоянно есть еда, вода и воздух в достаточных количествах, можете ли вы исключить ситуацию, что находясь в этом окружении задохнётесь от нехватки воздуха, умрёте от голода или жажды

Нет, не могу.


Другое дело, что эта аналогия неверна: ну не бывает стабильных окружений в программировании.


Он не умалчивает. Он всё ещё исключает возможность возникновения такого случая, так же, как это исключал код уровнем ниже.

И где сказано, что он исключает такую возможность?


Позиция разработчика при написании такого кода приблизительно такая: «тот, кто вызовет этот код должен позаботиться о доступности папки на запись, её наличии и о работоспособности накопителя».

Теперь посмотрим на код:


void Log(Event msg)
{
  foreach(var sink in _logSinks)
  {
     sink.Log(msg);
  }
}

О чем должен позаботиться тот, кто вызывает этот код?


Таким образом у функции есть 2 штатных результата: сохранён/не сохранён. И нештатный результат: невозможно сохранить/проверить.

Ээээ… "невозможно сохранить" — это "не сохранен". Это один и тот же результат.


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

… такие вещи проверяют не на уровне сохранения в БД.


Не любое… но многие из них!

Любое. Потому что, как сказано выше, ни к какому исключению нельзя дописать "это невозможно".


Но на то они и исключения, что бы их было мало, а не весь код был ими напичкан по 3 штуки на функцию!

В любом коде, написанном в стиле defensive programming исключений будет как минимум не меньше чем аргументов, у которых есть недопустимые значения, чаще — больше. И это, в общем-то, нормально, на то guards и существует.


совет новичкам по позиционированию исключений как таковых [...] А учитывая, что это работает и работает неплохо, значит в согласии других не очень нуждается

Под "работает неплохо" вы подразумеваете "работает у вас лично"? Так это еще не повод рассказывать это "новичкам" под соусом "как правильно".

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

Что, простите? Почему нельзя оценивать работу функцию по коду ошибки и при этом обрабатывать исключения? Или я не так понял.
Почему нельзя оценивать работу функцию по коду ошибки и при этом обрабатывать исключения?

Потому что возникает вопрос, какие ошибки возвращать кодом, а какие — исключениями. Нарушается униформность.


(понятное дело, что есть случаи, когда на униформность можно положить, но как основной гайдлайн — или везде одно, или везде другое)

Мы, наверно, по-разному понимаем.Например, мы работаем со списком, и просим вернуть номер элемента. Он может быть специальным, 0 или отрицательный, как признак ошибки — это фактически код возврата. При этом тот же объект может выкинуть исключение, скажем, ошибки доступа к памяти, которое нужно ловить по-любому, потому что надо освободить ресурсы. Или исключение при обращении к несуществующему элементу. Тут как ни крути, поддерживаешь обе парадигмы — только для «мягких» и «серезных» ошибко отдельно.
Например, мы работаем со списком, и просим вернуть номер элемента. Он может быть специальным, 0 или отрицательный, как признак ошибки — это фактически код возврата.

Какая у этого отрицательного номера семантика? "Не нашли" (при поиске)? Option[int] справится лучше. "Не смогли вставить, потому что не хватило памяти"? Лучше бросить исключение.


исключение, скажем, ошибки доступа к памяти, которое нужно ловить по-любому, потому что надо освободить ресурсы

Это если быть в рамках языка/платформы, где освобождение ресурсов ручное. А в .net, скажем, намного правильнее сделать IDisposable, который завернуть в using, и не ловить исключение — тогда как только выполнение покинет скоуп using, ресурсы будут освобождены сами.


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

Ну, я бы предпочел не поддерживать две парадигмы, если в этом нет фундаментальной необходимости.

Разумеется, это зависит от платформы.

Какая у этого отрицательного номера семантика? «Не нашли» (при поиске)?

Да, это удобно.

«Не смогли вставить, потому что не хватило памяти»? Лучше бросить исключение.

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

Не-а, "не найдено" — это не ошибка. И да, следствие этого подхода — это Option[T] TryFind и T Find (или, соответственно, Find и Get, если другая нотация) в одном интерфейсе, когда, если отсутствие ожидаемого объекта — это норма, то мы это явно проверяем, а если нет — то мы читаем, и не тратим лишний код в текущем скоупе на проверку.


И это вопрос не частоты/производительности (хотя, чего уж там, на них в реальности тоже приходится оглядываться), а удобства восприятия.

Не-а, «не найдено» — это не ошибка.

Спорно. Если я прошу дать мне номер элемента с ключом Х, то «не найдено» — это не номер элемента, это по сути ошибка.

И да, следствие этого подхода — это Option[T] TryFind и T Find (или, соответственно, Find и Get, если другая нотация) в одном интерфейсе, когда, если отсутствие ожидаемого объекта — это норма, то мы это явно проверяем, а если нет — то мы читаем, и не тратим лишний код в текущем скоупе на проверку.

От API зависит; я про конкретный случай написал с доступом по индексу. Так, просто для примера.
Если я прошу дать мне номер элемента с ключом Х, то «не найдено» — это не номер элемента, это по сути ошибка.

Ну вот в это и упирается обсуждение.


От API зависит

Именно. И поэтому в одном случае будет ошибка, а в другом — нет.


Но в общем, повторюсь, моя позиция сводится к следующему:


  • поддержка одной парадигмы легче, чем двух
  • когда мы что-то возвращаем как "ошибочное значение" (например, индекс ненайденного элемента), надо приложить все усилия к тому, чтобы это нельзя было, не подумав, запихнуть дальше в обработку (т.е., получили индекс, и сразу запихнули в "разрежь массив по индексу") — например, за счет типизации

PS


эта статья — совет новичкам по позиционированию исключений как таковых…

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

В чем отличие вас от "всезнающих программистов, которые учат новичков"?

В работоспособности идей. Я вырабатываю универсальные правила, и если обнаруживается, что они не работают, то не пытаюсь закрыть дыры тоннами «особых случаев», а просто отказываюсь от того, что не работает. Вот коллеги этим нередко грешат, когда с уверенным лицом дают общий совет, а когда ты удивлённо озвучиваешь им конкретную ситуацию, из-за которой интересовался, то начинается «а, ну это исключение… тут иначе надо...» и оказывается что данный ранее совет можно просто в мусор выбросить сразу :)
Также я немного скорректировал ту идею, о которой писал в статье и продолжаю ею пользоваться годами: любой код должен вызываться по контракту, и если обнаруживается, что контракт был нарушен (ему передали неверные данные или не обеспечили заявленное окружение), то код должен бросить исключение, но ни в каких других случаях исключения не уместны. По сути исключение — это когда код говорит «я не знаю, что с этим делать, решайте это без меня»
Ни разу не видел код, который бы работал иначе и не порождал бы при этом лишних проблем.
Ну и Вы просили ссылки предоставить ранее. Вот например Макконнелл в «Совершенный код» говорит в пункте 8.4 то же самое (ну или как минимум очень близкое). Там у него тоже про «Исключения используются в
таких же обстоятельствах, как и утверждения: для событий, которые не просто редко
происходят, а которые никогда не должны случаться»
Учитывая, что идею я излагал достаточно правильную (может неточную в некоторых мелочах), очень удивлён, что её так задизлайкали. Видимо что-то я сделал не так и где-то в самом изложении проблема.
Вот коллеги этим нередко грешат

Ну то есть вы заведомо считаете себя лучше коллег. Кул.


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

Учитывая, что контракт в большей части случаев определяет разработчик кода, понятие "неуместного исключения" становится бессмысленным.


Учитывая, что идею я излагал достаточно правильную

Не "правильную", а "мне кажется, что правильную".


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

Отвратительный перевод. Вот оригинал: "Exceptions are used in similar circumstances to assertions—for events that are not just infrequent but for events that should never occur." Assertion в этом контексте — это не утверждения, а отладочные проверки (Debug.Assert).


Что характерно там на абзац выше есть занятное:


Use exceptions to notify other parts of the program about errors that should not be ignored. The overriding benefit of exceptions is their ability to signal error conditions in such a way that they cannot be ignored

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

Ну то есть вы заведомо считаете себя лучше коллег. Кул.
Я лишь сообщил факт, Вы сейчас сами додумали остальное. Вообще утверждение «я лучше этого человека» звучит странно. Лучше в чём? :)
В том, что говорю только те вещи, в которых уверен, а если вдруг ошибаюсь, то признаю ошибки? — да, я крайне редко встречаю подобных людей и считаю это серьёзным преимуществом. Но ни о каком «я лучше» в общем понимании речи не идёт

Учитывая, что контракт в большей части случаев определяет разработчик кода, понятие «неуместного исключения» становится бессмысленным.
Это не совсем так. Показательный пример — функция валидации данных, которая в случае невалидности данных бросает исключение. А данном случае явное нарушение контракта, потому что сама функция теряет смысл и не должна была реализоваться, если ошибочность данных являлась недопустимой. Очень многие элементы контракта в коде устанавливаются не разработчиком, или устанавливаются им в неявном виде (как я привёл сейчас). Часто они продиктованы техзаданием, где описаны возможные события и состояния системы, а также описаны невозможные

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

Отвратительный перевод
это не меняет сути. Ну и по контексту там понятно, что они называют утверждениями (несколькими пунктами выше про assert речь шла). А вот сутью тут является «for events that should never occur»

Из этого занятного вытекает, что как только у вас есть другой способ сообщать вызываемому коду об ошибке, чтобы он не мог ее проигнорировать (например, типизация), исключения перестают быть, гм, исключительными.
почему они перестают быть «исключительными»? Вы или в это слово вложили непонятный для меня смысл, или сказали что-то странное. И связь с типизацией я не понял. Если компилятор в момент компиляции обнаружит несоответствие типов, он вообще код не скомпилирует, а вот если он по какой-то причине не обнаружит этого (ну скажем типизация прикручена не на уровне компилятора, а где-то в рантайме), то когда код будет вызван с неправильными типами, он как раз таки исключение и будет бросать, потому что передача параметра неправильного типа — это нарушение контракта.
Я лишь сообщил факт

Это не факт, это ваше мнение. Но не суть. Это не важно.


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

Показательный пример чего?


У меня есть объективная причина полагать, что я излагал правильную идею

Объективная?


Как минимум это подтверждается по меньшей мере одним уважаемым и рекомендуемым многими источником.

Неа, не подтверждается. Это вы его так трактуете, что подтверждается.


А вот сутью тут является «for events that should never occur»

… иии что?


почему они перестают быть «исключительными»?

Потому что есть другой механизм, который позволяет достичь того же поведения.


И связь с типизацией я не понял.

Вы понимаете, в чем предназначение исключений? Ну, по крайней мере, с точки зрения приведенной цитаты из МакКоннела?

Это не факт, это ваше мнение. Но не суть. Это не важно.
Странный у нас спор. Поверьте, это факт, я это слышал собственными ушами, а не придумал где-то у себя в голове и решил, что это так. Они иногда мне дают советы, которые не работают в конкретных ситуациях — это факт. Также фактом является то, что они не могут иногда указать критерий, по которому бы отделили те случаи, когда совет работает от этого нерабочего. Они просто говорят, что это исключение и обычно работает. Это всё факты, а не мои предположения. Моим предположением было бы, если бы я сказал «это значит, что они не смыслят в программировании» — вот это было бы уже мнение, а не факт, потому что данные события не доказывают их некомпетенстность.

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

Объективная?
да, я её изложил. Объективная потому, что я не сам её выдумал, а сослался на источник, который находится вне меня (вне субъекта)

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

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

Вы понимаете, в чем предназначение исключений? Ну, по крайней мере, с точки зрения приведенной цитаты из МакКоннела?
Естественно. В том же, о чём писал я. Чтобы обработать в коде ситуацию, которая не должна была возникнуть и с которой этот код не предназначен справляться. Например когда при попытке сохранить изменения в БД рвётся соединение, тогда бросается исключение имеющее смысл «мне обещали, что предоставят ресурс для сохранения данных, но не предоставили — разбирайтесь кто из вас лажанул» (это так, простыми словами :)) )

P.S. Последнее Ваше сообщение похоже на троллинг. Давайте более содержательно, пожалуйста. Я же отвечал на Ваше сообщения и все эти «примера чего?» и «иии что?» содержали ответ в Вашем же сообщении, на которое я отвечал :)
Того, что программист далеко не всегда может сам решать каким будет контракт.

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


Объективная потому, что я не сам её выдумал, а сослался на источник, который находится вне меня (вне субъекта)

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


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

Да, они там нужны. Но не только там. Это важно, если что.


«исключительные» значит «те которые исключаются, находятся вне основной массы».

А почему они находятся вне основной массы? По функционированию, не по предназначению?


Естественно. В том же, о чём писал я. Чтобы обработать в коде ситуацию, которая не должна была возникнуть и с которой этот код не предназначен справляться.

Nope. В этом и беда. Предназначение исключения как программного инструмента — это создать сигнал, который вызывающий код не может случайно проигнорировать (только намеренно). И это как раз написано у МакКоннела, я приводил цитату. А вот исходя из этой особенности исключений мы и выбираем, для чего их использовать — для ситуаций, которые мы считаем, что игнорировать нельзя.

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

Объективен здесь только тот факт, что у МакКоннела написана такая фраза
Объективно то, что он излагает ту же идею: что исключение должно генерироваться только тогда, когда произошло что-то, чего не должно было произойти. И объективен тот факт, что эту книгу часто рекомендуют среди программистов к прочтению (она в топе рекомендаций). И потому у меня есть объективные причины полагать, что я прав (объективные потому, что писал книгу не я, и в топ выводил её не я, то есть это произошло без моего участия)

Да, они там нужны. Но не только там. Это важно, если что.
По Макконнеллу они нужны только там. Я считаю, что они нужны только там. Тысячи людей, которые читали эту книгу и рекомендуют её, считают, что в книге даются хорошие советы, значит они не увидели в ней ничего «еретического», значит считают эту идею как минимум неплохой. И только Вы сейчас настаиваете на том, что это не так. Предложите свой источник того же уровня, возможно другое мнение и правда существует в профессиональной среде, но пока у меня нет причин так считать.

А почему они находятся вне основной массы? По функционированию, не по предназначению?
по назначению. Они используются для описания исключений из правил. Просто вдумайтесь в название exception. ну и во фразу «it is exceptional situation» и «you should create an exception for it». Потому оно называется в английском «exception», а у нас «исключение», потому что именно для описания исключений предназначено, а не просто как часть функционального кода. То есть мы создаём объект исключения с описанием исключительной ситуации, которая произошла, а потом бросаем этот объект вверх, пока его кто-то не словит.

Предназначение исключения как программного инструмента — это создать сигнал, который вызывающий код не может случайно проигнорировать (только намеренно)
то есть для того, чтобы не обработать исключение, мне надо написать дополнительно код? Забавно, я думал мне приходится писать код для того, чтобы словить его и обработать где надо :) Получается я для каждого блока кода должен писать не список исключений, которые хочу ловить, а список исключений, которые ловить не хочу? Иначе я вполне могу забыть в вызывающем коде обработать этот сигнал, он поднимется на самый верх и заруинит программу. То есть этот сигнал может быть не обработан ни на одном уровне программы, при чём не специально, а просто по ошибке (забыли при написании кода добавить try...catch)
Перечитайте беседу, я это уже писал… дважды. В том числе в том комментарии, на который Вы этой фразой ответили

Там написано только "тут контракт функции явно противоречит её сути". Но проблема этого объяснения в том, что это ваше представление о сути функции. Я с ним не согласен.


Объективно то, что он излагает ту же идею: что исключение должно генерироваться только тогда, когда произошло что-то, чего не должно было произойти.

Нет, не излагает. Я приводил точную цитату, там нет ничего про "только". Вот цитата, которая наиболее близка к вашему утверждению:


Throw an exception only for conditions that are truly exceptional. Exceptions should be reserved for conditions that are truly exceptional—in other words, for conditions that cannot be addressed by other coding practices.

(и в этом виде она невыполнима, если что)


Предложите свой источник того же уровня

.NET BCL. Пример с ключом в словаре (в котором нет ничего исключительного) — он оттуда. И миллион других бросаемых ими исключений.


по назначению. Они используются для описания исключений из правил.

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


потому что именно для описания исключений предназначено

Проблема в том, что вы устроили рекурсию. "Исключение предзначено для описания исключения", но вы не объясняете, что такое "исключение".


то есть для того, чтобы не обработать исключение, мне надо написать дополнительно код?

Чтобы проигнорировать исключение, вам понадобится написать дополнительный код. По крайней мере, в .NET так.


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


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

И именно это означает, что вы (программа, как вызывающий код) не можете его проигнорировать.


Просто для сравнения. Вот код без исключений, с кодом ошибки:


ErrorCode TryWriteFile(string path);
//...
TryWriteFile(somePath);
//ничего не обработали, пошли дальше
//ошибка произошла - мы не заметили
SendWrittenFile(somePath); //тут что-то пойдет не так

Вот пример с исключением:


void WriteFile(string path); //throws Exception
//...
WriteFile(somePath);
//ничего не обработали, пошли дальше
//ошибка произошла - мы НЕ пойдем дальше
SendWrittenFile(somePath); //тут не будет скрытой ошибки, потому что мы сюда не попадем

Так понятнее про игнорирование?

там нет ничего про «только».
Throw an exception only for conditions that are truly exceptional. Exceptions should be reserved for conditions that are truly exceptional—in other words, for conditions that cannot be addressed by other coding practices.

ну да, совсем нет.
.NET BCL. Пример с ключом в словаре (в котором нет ничего исключительного) — он оттуда. И миллион других бросаемых ими исключений.
И он ничего не доказывает, потому что всего лишь означает, что позиция разработчиков в том, что не надо эту функцию дёргать не получив список существующих ключей. Я с этим инструментом не знаком, но могу предположить, что это что-то наподобие как попробовать запросить несуществующий ключ реестра или значение элемента массива с индексом больше максимального (выйдя за его пределы)

Возможно, кто-то использует (программные) исключения для описания исключений из правил. Но это точно не единственное их применение.
Тут снова же Ваши слова против текста профессиональной книги из топа. Вы всё ещё ссылаетесь лишь на своё мнение, что вот нисколько не аргумент, потому что я могу так же начать ссылаться исключительно на своё мнение и беседа превратится в тупое бодание двух упрямых баранов :) Потому давайте или подкреплять аргументами слова, или хотя бы ссылками на источники более достоверные, чем «потому что я так сказал».

Проблема в том, что вы устроили рекурсию. «Исключение предзначено для описания исключения», но вы не объясняете, что такое «исключение»
это не рекурсия, тут слово исключение имеет разные значения. Объект исключения (в коде) предназначен для описания исключительной ситуации (в логике программы). «Объект исключения» я думаю расшифровывать не нужно, а вот «исключительная ситуация» — это такая ситуация, которая не должна была произойти (возникновение которой исключается задачей, когда вам говорят «напишите программу сложения двух целых чисел», а на вход присылают «2.5 + 4.7» — вот это исключительная ситуация, которая по условия не должна возникать)

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

Именно. Там нет ничего про "только для ситуаций, которые не могут произойти".


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

Нет, не означает. Это означает, что еесли вы вызываете этот метод, все ожидают, что у вас валидный ключ. Получать список для этого не надо (есть другие методы, в конце концов).


Но, самое главное: это не исключительная ситуация. Это происходит десятки раз в день. В этом нет ничего "truly exceptional".


но могу предположить, что это что-то наподобие как попробовать запросить несуществующий ключ реестра или значение элемента массива с индексом больше максимального

А в ситуации "запросить несуществующий ключ реестра" тоже нет ничего исключительного.


Тут снова же Ваши слова против текста профессиональной книги из топа

Нет, против вашей трактовки этого текста. Там нет ничего про "единственное применение — для исключений из правил".


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

.NET BCL.


«Объект исключения» я думаю расшифровывать не нужно, а вот «исключительная ситуация» — это такая ситуация, которая не должна была произойти

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


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

Нет. Какой код вы считаете "не предназначенным справляться с ситуацией" — вызывающий или вызываемый?

всё, Вы начали отрицать факты. беседа потеряла смысл. Я Вам выделяю в тексте слово «только», а Вы отвечаете, что там нигде не сказано «только». И ладно когда Вы это сказали первый раз, и я подумал, что Вы просто пропустили, но когда я Вам его выделил, а Вы это повторили — значит Вы не пропустили, а просто игнорируете то, что противоречит Вашим убеждениям. Точно также Вы уже начали спорить не со мной, а с Макконнеллом на тему truly, при чём говорите, что спорите с моей интерпретацией его текста, но сами ставите под сомнение сам текст в явном виде.
Видимо время завершать беседу.
Я Вам выделяю в тексте слово «только», а Вы отвечаете, что там нигде не сказано «только».

Нет, я отвечаю, что не сказано "только для этого". Потому что там сказано "только для другого".


Точно также Вы уже начали спорить не со мной, а с Макконнеллом на тему truly

Нет, с МакКоннелом я не спорю. Я утверждаю, что ситуация, в которой исключения используются в .NET BCL противоречит критериям, описанным в приведенной цитате.

Потому что там сказано «только для другого»
процитируйте пожалуйста и выделите слово «другого» жирным, как я сделал для Вас, когда выделял слова «только», «по настоящему» и «не может».

Я утверждаю, что ситуация, в которой исключения используются в .NET BCL противоречит критериям, описанным в приведенной цитате.
хорошо, но я не могу этого проверить. Приведите тогда цитату из книги уровня «Совершенный код», где бы в явном виде говорилось, что вот НЕ ТОЛЬКО. Потому что Вы сейчас даёте конкретную реализацию, и мы начинаем гадать почему она такая. Вы говорите, что такое случается по 10 раз в день, а вдруг сами разработчики Вам ответят «так это неучи наш инструмент пользуют, и разумеется это не должно случаться в принципе, они просто на коленке код пишут» и в этом случае вся Ваша теория рассыпется, но мы этого проверить не можем, ведь смотрим на код и гадаем ))
процитируйте пожалуйста и выделите слово «другого» жирным, как я сделал для Вас, когда выделял слова «только», «по настоящему» и «не может».

Да пожалуйста.


Throw an exception only for conditions that are truly exceptional. Exceptions should be reserved for conditions that are truly exceptional—in other words, for conditions that cannot be addressed by other coding practices.

Приведите тогда цитату из книги уровня «Совершенный код», где бы в явном виде говорилось, что вот НЕ ТОЛЬКО.

"Не только" что?


Начнем с того, что в главе 8.4 есть "только" два места, где упоминается слово "только". Одно я процитировал выше. Второе — вот:


For compatibility with other languages, consider throwing only objects derived from the Exception base class.

… и оно относится не к условиям употребления, а к тому, что бросать. Проще говоря, за пределами нашего разговора.


Так что вопрос начинается с того, какое же "только" озвучивает МакКоннел. То, которое я процитировал выше?


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


  1. Use exceptions to notify other parts of the program about errors that should not be ignored
  2. Throw an exception only for conditions that are truly exceptional. Exceptions should be reserved for conditions that are truly exceptional—in other words, for conditions that cannot be addressed by other coding practices. Exceptions are used in similar circumstances to assertions—for events that are not just infrequent but for events that should never occur.

В первом пункте нет ограничения "только если". Он отвечает на ваш вопрос.


PS Цитата оттуда же, которая явно говорит, что исключения — часть контракта:


The exceptions thrown are part of the routine interface, just like specific data types are.
Цитата оттуда же, которая явно говорит, что исключения — часть контракта
мы это с Вами обсуждали уже. Это как указание неустойки является частью договора, так и описание исключений является частью контракта. И я Вам уже ответил, что в такой значении я вполне с этим согласен, но я говорил подразумевая другое значение контракта — той части контракта, которая является его сутью, как в договоре сам объект договора. Вот когда договорённость в отношении самого объекта договора нарушается, тогда и обращаются к пункту с неустойками. Так и тут, когда нарушается сама суть контракта (кто кого и как должен вызвать и что должны сделать или вернуть), тогда и обращаемся к пункту с исключениями (как на это нарушение реагировать).

for conditions that cannot be addressed by other coding practices
где вы тут увидели «только для другого»? :) Вы копируете разные части фразы, объединяете в них отдельные слова и говорите, что их надо так понимать. Там говорится «только для этого», а потом «если это нельзя решить другим способом», а у Вас это как-то скомпилировалось в «только для другого».

«Не только» что?
не только в этих случаях исключения нужно бросать. Подтвердите, что уважаемые цитируемые источники это утверждают, потому что сейчас Вы это доказываете через свои догадки. Вы показываете реализацию, а потом начинаете рассуждать о чём думали авторы кода, когда его писали (что они знали что это нисколько не «по-настоящему исключительная» ситуация), хотя на самом деле они могли считать ровно обратное, и код часто приводящий к выбросу этого исключения считать никчёмным и непрофессиональным.
То есть факты:
1. их код бросает это исключение (верю, проверять не буду)
2. это исключение в реализациях возникает часто (верю)
Но дальше пошли догадки:
так и должно быть, и они это знали (не верю, предъявите доказательство… я не верю, что так и задумывалось)
И я Вам уже ответил, что в такой значении я вполне с этим согласен, но я говорил подразумевая другое значение контракта — той части контракта

Вот понимаете ли, у вас свое понимание "контракта", не то, которым я и мои коллеги пользуемся.


где вы тут увидели «только для другого»

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


Подтвердите, что уважаемые цитируемые источники это утверждают

"Лихко".


An exception is any error condition or unexpected behavior that is encountered by an executing program. Exceptions can be thrown because of a fault in your code or in code that you call (such as a shared library), unavailable operating system resources, unexpected conditions that the runtime encounters (such as code that can't be verified), and so on.

Handling and throwing exceptions in .NET.


А вот специально для вас и политики с кодами ошибок:


Throw exceptions instead of returning an error code
Exceptions ensure that failures do not go unnoticed because calling code didn't check a return code.

Best practices for exceptions

Тут написано «только для вещей, которые не могут быть сделаны другими практиками». Это «другое», нежели ваше " только тогда, когда произошло что-то, чего не должно было произойти.".
В книге есть и та и другая фразы :) То есть наличие одной не отменяет наличие другой. Вот цитата из оригинала книги Exceptions are used in similar circumstances to assertions—for events that are not just infre quent, but that should never occur. Я где-то ранее в беседе уже приводил это на русском языке.

Вот понимаете ли, у вас свое понимание «контракта», не то, которым я и мои коллеги пользуемся.
хорошо, я не могу найти подходящего слова, чтобы заменить им слово «контракт» в своём утверждении, потому давайте уйдём от моего определения через «контракт» и обсудим само утверждение по факту: должно ли исключение бросаться ТОЛЬКО в ситуации, которая не должна была произойти, или НЕ ТОЛЬКО. Ведь важно это, потому что если я неправ, то никому уже не будет интересно правильное ли я слово подобрал или неправильное, если сама идея ошибочна. Если же я прав, то я всегда могу перефразировать это так, чтобы донести мысль до другого человека :)

«Лихко».
не увидел подтверждения Ваших слов. Там в тексте говорится «An exception is any error condition or unexpected behavior that is encountered by an executing program». То есть в явном виде сказано про ошибку или неожиданное поведение с которым столкнулась программа. Это то, о чём я и говорю «ТОЛЬКО то, что не должно было произойти».

Throw exceptions instead of returning an error code
Exceptions ensure that failures do not go unnoticed because calling code didn't check a return code.
обратите внимание на выражение error code (не status code, а именно error code). Я бы сказал, что тут речь про ошибки, которые не должны в программе возникать (а не ошибки валидации данных, например, которые скорее являются особым статусом данных, а не ошибкой), но вообще тут может иметься ввиду как одно, так и другое. Почему Вы считаете, что тут имеются ввиду именно ошибки в Вашем понимании? Просто я вижу это так, что если понимать ошибку в широком смысле, то любой отрицательный ответ может быть интерпретирован как ошибка и тогда это превращается в «не возвращайте false, возвращайте или true или бросайте исключение». Ну то есть в таком понимании ошибки утверждение приближается к абсурду.
Вот цитата из оригинала книги Exceptions are used in similar circumstances to assertions—for events that are not just infre quent, but that should never occur.

А в этой фразе нет "только".


должно ли исключение бросаться ТОЛЬКО в ситуации, которая не должна была произойти, или НЕ ТОЛЬКО

Что такое "ситуация, которая не должна была произойти"? Как это формально определить?


В функцию валидации должны были или не должны были переданы невалидные данные? В метод, возвращающий значение из словаря, должен был, или не должен был быть передан несуществующий ключ? В http-клиент должен быть или не должен быть передан невозможный URI? И так далее до бесконечности.


Это то, о чём я и говорю «ТОЛЬКО то, что не должно было произойти».

Нет. Нет никакой причины считать, что "any error condition" — это то, что "не должно было произойти".


Почему Вы считаете, что тут имеются ввиду именно ошибки в Вашем понимании?

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


Более того, вы тоже используете выражение "код ошибки". Напомню:


тут нет причины бросать исключение и вполне логично вернуть код ошибки

Так вот, цитата из гайда (напомню: "Throw exceptions instead of returning an error code") явно противоречит вашему "логично".


то любой отрицательный ответ может быть интерпретирован как ошибка

Может быть. Но это будет неверная (потому что бесполезная) интерпретация.

А в этой фразе нет «только»

зачем эта фраза нужна, если «не только»? Это как сказать «ткань нужна для шиться одежды» и забыть сказать, что она используется ещё для 100 других целей.
Если бы там было сказано «для событий, которые не должны были произойти, используются исключения», то Ваше «не только» было бы вполне уместно, но когда там сказано «исключения используются для событий, которые не должны были произойти», то это описание назначения исключений. Это значит что для чего-то другого их использовать не нужно, они вот именно для этого.

В функцию валидации должны были или не должны были переданы невалидные данные?
это же функция валидации. Она предназначена для того, чтобы в неё невалидные данные попадали, иначе она не нужна вовсе, если бы там только валидные всегда были.

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

В http-клиент должен быть или не должен быть передан невозможный URI?
туда должен был попасть URI соответствующий стандарту, но при этом вполне может попасть несуществующий. При этом проверка на соответствие стандарту должна была произойти на этапе валидации, где несоответствие стандарту вполне ожидаемо (снова же, иначе функция валидации была бы не нужна). То есть тут всё вполне ожидаемо и предсказуемо :)

Нет. Нет никакой причины считать, что «any error condition» — это то, что «не должно было произойти».
ошибки и неожиданное поведение — это то, чему положено иногда происходить? Тогда почему это называется «ошибка» и «неожиданное поведение»? Мы заведомо пишем код с ошибками, и удивляемся событиям, которым положено периодически происходить? )

А что такое «ошибка в моем понимании»
в широком понимании, когда Вы под ошибкой понимаете не то, что какая-то часть системы сделала что-то не так, а то, что любой статус отличный от «успех» является ошибкой?

Может быть. Но это будет неверная (потому что бесполезная) интерпретация.
но Ваша интерпретация «ошибка — это то, что иногда происходит» как раз к этому и ведёт. Вы говорите, что ошибки — это часть стандартного поведения программы. Тогда поясните где грань между ошибкой и не ошибкой? Расшифруйте тут значение слова error короче :)
зачем эта фраза нужна, если «не только»?

Как рекомендация. Я и говорю: вы трактуете текст. Это субъективно.


Это значит что для чего-то другого их использовать не нужно, они вот именно для этого.

Нет, не значит.


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

Нет, она предназначена для того, чтобы пропустить дальше валидные данные.


не должен был. Если он был передан, то это произошло по ошибке

Неверно. Словарь выступает кэшом (в конкретном сценарии), и вы наблюдаете cache miss. Это не ошибка.


То есть тут всё вполне ожидаемо и предсказуемо

Не, не предказуемо. Что будет, если там несуществующий хост?


ошибки и неожиданное поведение — это то, чему положено иногда происходить?

Да.


Тогда почему это называется «ошибка» и «неожиданное поведение»?

Потому что первое — это не happy path, а второе — это то, что мы не смогли предусмотреть.


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

Мы заведомо знаем, что достаточно сложная система не безошибочна и не безотказна.


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

Статус чего? Если выполнения операции, то да. А если операции, то нет.


но Ваша интерпретация «ошибка — это то, что иногда происходит» как раз к этому и ведёт.

Нет, не ведет.


Тогда поясните где грань между ошибкой и не ошибкой?

Там, где мы не можем выполнить необходимую операцию.


Типичный пример: есть удаленный сервис, который обычно отвечает меньше чем за секунду, но иногда — больше. Мы с ним работаем, и нам надо вернуть свой ответ за секунду с мелочью. Мы ставим таймаут на вызов этого сервиса в нужные нам 0.95с. Если сервис за это время не ответил — это ошибка, мы не можем выполнять свою задачу, и рапортуем дальше по стеку, что случилась фигня. Но это происходит не просто "иногда", а достаточно часто (просто это укладывается в наш 99% SLO).

Как рекомендация. Я и говорю: вы трактуете текст. Это субъективно.
Нет, это указание назначения. Я не трактую текст, а разбираю его исходя из законов логики, то есть максимально объективно.

Нет, не значит.
Нет. Значит! :)

Нет, она предназначена для того, чтобы пропустить дальше валидные данные.
Нет, она предназначена для того, чтобы не пропустить невалидные

Неверно. Словарь выступает кэшом (в конкретном сценарии), и вы наблюдаете cache miss. Это не ошибка.
а вот гугл хром считает это ошибкой, называется ERR_CACHE_MISS

Не, не предказуемо. Что будет, если там несуществующий хост?
предсказуемо — вернётся статус 404, как при любом URI ведущем в пустоту

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

Статус чего? Если выполнения операции, то да. А если операции, то нет.
чем статус операции отличается от статуса выполнения операции?

Нет, не ведет.
ведёт

Там, где мы не можем выполнить необходимую операцию.
Правильно ли я понял, что речь о чём-то, что обязательно должно быть сделано (альтернатив нет), но при этом сделано быть не может?

Но это происходит не просто «иногда», а достаточно часто (просто это укладывается в наш 99% SLO).
оу… так «не должно происходить» никак не связано с частотой происходящего. Это лишь значит, что сервер ДОЛЖЕН успеть ответить за это время и ситуация, когда он ответить не успел возникать НЕ ДОЛЖНА. При этом он может хоть в 100% случаев не отвечать, это не мешает нам бросать там исключение. При этом с программой может работать мега ответственный и внимательный человек, который допускает одну опечатку в год (то есть ошибка происходит один раз на сотни тысяч вызовов), но это не мешает нам обрабатывать ситуацию с опечатками штатно, потому что опечатки — это не то, что НЕ ДОЛЖНО происходить, а то, что вполне может быть (если по ТЗ не заявлено обратное, конечно). То есть дело не в частоте, а в «должно» / «не должно». Дело в обещанных коду условиях и в их отличии от реально предоставленных. Если отличие критично — исключение. Если не критично — всё ок, просто статус ответа.
Я не трактую текст, а разбираю его исходя из законов логики, то есть максимально объективно.

"Разбирать исходя из законов логики" — это и есть "трактовать". Потому что вы додумываете то, чего в тексте нет.


Нет. Значит!
Нет, она предназначена для того, чтобы не пропустить невалидные

Ну вот видите. Нет объективного способа определить, кто из нас прав.


а вот гугл хром считает это ошибкой, называется ERR_CACHE_MISS

Мне все равно, что думает хром.


предсказуемо — вернётся статус 404, как при любом URI ведущем в пустоту

Напомню, что вопрос был "что будет, если там несуществующий хост?" Статус 404 возвращает хост, но он не существует. Так что же будет?


чем статус операции отличается от статуса выполнения операции?

Тем, что статус выполнения операции — понятие технологическое, и для синхронной операции сводится к "выполнилась/не выполнилась" (для асинхронной немного сложнее), а вот статус операции — это прикладное понятие, и там могут быть произвольные значения.


Правильно ли я понял, что речь о чём-то, что обязательно должно быть сделано (альтернатив нет), но при этом сделано быть не может?

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


так «не должно происходить» никак не связано с частотой происходящего.

В таком случае под "не должно происходить" разработчик контракта может написать что угодно. Что возвращает нас к исходному тезису: нет формального разделения между "здесь эксепшн" и "здесь код ошибки".


Если отличие критично — исключение.

Что значит "критично"? Как это формально определить?

Ну вот видите. Нет объективного способа определить, кто из нас прав.
Почему нет? Убираем из кода функцию валидации и смотрим что изменилось. Вот то, что изменилось, то и было назначением функции (то она и делала). Если убрать функцию валидации, то мы начнём получать невалидные данные и падать ошибками :) В отношении валидных данных ничего не изменится.

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

Статус 404 возвращает хост, но он не существует. Так что же будет?
Тут зависит от того, должен ли он был существовать. На уровне кода, который выполняет непосредственно запрос — это ошибка (нужно бросать исключение). На более высоких уровнях это может переставать быть ошибкой, например на уровне, где код знает, что адреса вбиваются человеком, это может уже не считаться ошибкой. Но на уровне самого http клиента да, это ошибка.

В таком случае под «не должно происходить» разработчик контракта может написать что угодно.
Не что угодно. Если в ТЗ написано «файл периодически может быть недоступен и это надо предусмотреть», то это значит, что на каком-то уровне программы (компонента) это точно не будет считаться ошибкой. Если же подобное не сказано — считать ошибкой на всех уровнях программы.
Если сказано «данные будут направляться другой частью системы по АПИ, и потому гарантированно будут верны», то это причина считать ошибкой любые невалидные данные и бросать исключение, а наверху ловить его и отдавать ответ «направлены неверные данные, ошибка 153». И тут было бы странно передавать статусы по цепочке вверх, потому что такое поведение было бы усложнением логики на пустом месте. Вы сейчас говорите что-то вроде «но программист может принимать любые решения, кто ему запретит?» )) с этим я согласен, может… но только одни решения лучше других. Программист может вообще принять волевое решение не использовать исключения, но код от этого лучше не станет. Он может принять решение, что при генерации случайного числа он будет не просто генерировать число, а он будет генерировать 1000 чисел, потом генерировать число от 1 до 1000, а потом брать число с соответствующим номером. Опять же да, может, но код лучше от этого точно не станет )) Также он может от балды принимать решение о том, что должно происходить, а что не должно, не учитывая назначение кода и условия описанные в задаче, но код от этого тоже станет только хуже :) Да, местами у программиста и правда есть варианты считать ли что-то недопустимым или не считать, и в этом случае пускай решает, но зачастую такое решение уже предопределено задачей, которая ставится перед написанным кодом.

С точки зрения кода, бросающего эксепшн (это важно), это то, что обязательно должно быть сделано.
с этим вполне согласен. Предлагаю не спорить на тему того нужен ли эксепшин, если код не может выполнить то, ради чего существует, потому что в этом месте я ничего не оспариваю. Ну то есть создавая код, мы рассчитываем на то, что он будет работать, а значит когда он не работает, это то, что «не должно происходить».

Что значит «критично»? Как это формально определить?
это то, о чём Вы говорили. Есть случаи, когда код может выполнить поставленную перед ним задачу, а есть тогда, когда не может. Если может — не критично. Если не может — критично.
Есть случаи, когда код может выполнить поставленную перед ним задачу, а есть тогда, когда не может. Если может — не критично. Если не может — критично.

А теперь вернемся к более раннему вашему утверждение:


Например когда происходит валидация массива значений, но сразу же обнаруживается, что там не массив, или когда был введён адрес АПИ к которому нужно было сделать запрос, но сам адрес оказался ошибочным и потому выполнение запроса невозможно и локальный код приходится завершать. Но если такие ситуации были предусмотрены по контракту, то тут нет причины бросать исключение и вполне логично вернуть код ошибки (которых там может быть 100 штук, начиная от недоступности АПИ и завершая ответом от АПИ, что пользователь неправильно ввёл пароль или указал недопустимый возраст).

Как объективнно отличить ошибки, при которых надо бросать эксепшн, от ошибок, при которых надо возвращать "код ошибки"?

Как объективнно отличить ошибки, при которых надо бросать эксепшн, от ошибок, при которых надо возвращать «код ошибки»?
По всем ошибкам надо бросать исключения, если под словом «ошибка» понимать то, что я озвучил в соседнем сообщении — поведение системы, которое не прогнозировалось при её создании. Если мы планировали (рассчитывали на то), что при вызове метода ему может быть передан идентификатор несуществующего сокета, то не нужно бросать эксепшины с криками «нет такого сокета», а нужно обработать ситуацию в соответствии с планом. Если же мы такого не планировали и методу должен передаваться только ид существующего сокета — тогда определённо эксепшин, если сокета нет
Если мы планировали (рассчитывали на то), что при вызове метода ему может быть передан идентификатор несуществующего сокета, то не нужно бросать эксепшины с криками «нет такого сокета», а нужно обработать ситуацию в соответствии с планом.

А если план — "бросить эксепшн"?


Т.е. мы планировали, что система, вместо того, чтобы не глядя открыть несуществующий сокет, и упасть с чем-нибудь непонятным (или, хуже того, продолжить работу), бросит нам понятный эксепшн "такого сокета нет"?


Ну и да, места для кодов ошибок все равно нет.

А если план — «бросить эксепшн»?
если Вы бросаете исключение по плану, то это не исключение, а часть плана. Это значит, что Вы используете исключение как замену инструкции goto и просто создаёте запутанный и некачественный код (как тот человек, который результат валидации бросает эксепшинами, чтобы не передавать результат по цепочке наверх… вот он по плану эксепшин и бросает, только код от этого лучше не становится).

Мы всё ещё продолжаем странную беседу, когда я говорю «вот так правильно, это делает код лучше, потому что...», а Вы мне зачем-то отвечаете «а что если я делаю неправильно? как Вы это объясните?» — да никак :) ну точнее только так, что Вы это делаете неправильно, и результат будет хуже (менее читабельный, более сложный в сопровождении).

Если Вы предусмотрели ситуацию в коде и можете её обработать — обрабатывайте. Если не можете — бросайте эксепшин. Но бросит эксепшин — это не обработать. Это как называть себя победителем в состязании, в котором не участвовал ))

Т.е. мы планировали, что система, вместо того, чтобы не глядя открыть несуществующий сокет, и упасть с чем-нибудь непонятным (или, хуже того, продолжить работу), бросит нам понятный эксепшн «такого сокета нет»?
нет, мы планировали, что она откроет существующий сокет. Но если что-то пойдёт не по плану, то она выкинет эксепшин. Если Вы планировали проверить работу сокета, если не работает — бросить эксепшин, а если работает — отправить данные, то должны были назвать функцию не sendData, а throwExceptionOnUnavailablSocketOrSendDataOtherwise. Так что говоря, что бросить эксепшин являлось частью плана — Вы просто лукавите, потому что об обратном даже название функции говорит.

Ну и да, места для кодов ошибок все равно нет.
в том значении слова «ошибка», которое я приводил — да, места кодам ошибок нет. В значении «код предназначен для запроса документа по id, но при запросе документ с таким id был не найден, а значит код не выполнил предназначение и это ошибка» — в этом понимании ошибки код ошибки вполне может быть возвращён, потому что на самом деле функция выполнила своё предназначение и документ запросила, а то, что его там не было — не её вина. Она может предусматривать возвращение пустого объекта и передачу статуса произошедшей ошибки кому-то, и это будет правильно.
Это значит, что Вы используете исключение как замену инструкции goto

Но почему? Я не использую исключение, чтобы перейти куда-то.


ну точнее только так, что Вы это делаете неправильно

Основная проблема в том, что это вы считаете, что я делаю неправильно; в то время как я делаю в соответствии с гайдлайнами платформы, для которой я пишу. Странно, правда?


Если Вы планировали проверить работу сокета, если не работает — бросить эксепшин, а если работает — отправить данные, то должны были назвать функцию не sendData, а throwExceptionOnUnavailablSocketOrSendDataOtherwise.

Нет, не должен был. Это противоречит гайдлайнам.

Но почему? Я не использую исключение, чтобы перейти куда-то.
потому что если Вы бросаете исключение «по плану», то Вы это делаете для того, чтобы остановить выполнение кода, перескочить на несколько уровней выше и там что-то сделать. Это и есть обычный goto. А вот если Вы кидаете исключение потому, что что-то пошло не по плану, то тогда Вы говорите «я по плану работу провести не могу, потому разбирайтесь там выше почему вы мне не подготовили среду для работу». Каждый раз, когда Вы будете бросать эксепшин по плану, именно планируя, что код выше его как-то конкретным образом обработает — Вы будете писать неправильный код. Момент генерации исключения — это место, где у Вас больше нет никакого плана и Вы понятия не имеете что будет дальше.

Основная проблема в том, что это вы считаете, что я делаю неправильно; в то время как я делаю в соответствии с гайдлайнами платформы, для которой я пишу. Странно, правда?
Отвечу как Вы мне ранее: «это Ваше субъективное мнение, что Вы пишите в соответствии с гайдлайнами».

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

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


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

Я не планирую, что его где-то кто-то как-то обработает, с чего вы это взяли?


Отвечу как Вы мне ранее: «это Ваше субъективное мнение, что Вы пишите в соответствии с гайдлайнами».

Да. И у вас нет никакого объективного способа доказать мне, что я неправ.


докажите ссылкой, пожалуйста

Уже приводил. Jeffrey Richter, CLR via C#, Fourth Edition, Microsoft Press, 2012. Глава 20, "Exceptions and State Management":


When an action member cannot complete its task, the member should throw an exception.
Я не планирую, что его где-то кто-то как-то обработает, с чего вы это взяли?
Вы сами сказали, что бросить эксепшин — это часть плана. Вы считаете, что когда бросили эксепшин, то решили поставленную задачу? Если нет, но при этом утверждаете, что сам эксепшин является часть плана, значит просто обязаны что-то планировать дальше для решения задачи. Или у Вас в планах есть вариант «не решать задачу»? )

Да. И у вас нет никакого объективного способа доказать мне, что я неправ.
это был Ваш аргумент, а не мой. Странно говорить, что если я не доказал, что Ваш аргумент неверен, то он верен. )

When an action member cannot complete its task, the member should throw an exception.
должен ли метод getLastError бросить исключение, если до его вызова не произошло ни одной ошибки? По сути я ведь не могу получить последнюю ошибку, если ошибок не было, а значит этот метод не выполняет заявленную в названии задачу. Должно быть исключение? Или он может вернуть статус NO_ERRORS?
А getLastId должен бросить исключение, если нет ни одной записи (ни одного id ещё нет), или он может вернуть 0, чтобы код мог прибавить 1 и добавить первую запись?
Вы сами сказали, что бросить эксепшин — это часть плана. Вы считаете, что когда бросили эксепшин, то решили поставленную задачу?

Да. Я выполнил контракт метода.


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

Зависит от его контракта.


А getLastId должен бросить исключение, если нет ни одной записи (ни одного id ещё нет), или он может вернуть 0, чтобы код мог прибавить 1 и добавить первую запись?

Операция Enumerable.Last, например, бросает ошибку, если в множестве нет элементов.

Да. Я выполнил контракт метода.
название метода было PerformTheContract? Если нет, то почему Вы в методе решаете эту задачу. Какова была задача метода, и как эту задачу можно решить бросив исключение? Приведите хоть один пример (ну кроме того комедийного, который я сам выше привёл)

Зависит от его контракта.
как так, если Вы ранее сами цитировали источник, где сразу после процитированной Вами фразы идёт правило взятое в рамочку
An exception is when a member fails to complete the task it is supposed to
perform as indicated by its name.
и кажется Вы эту фразу даже где-то выше сами также цитировали (но это не точно). Так всё же этот источник говорит правду или врёт? Следуя его рекомендациям нужно ли выбросить эксепшин из метода getLastError, если ошибок ещё не было и возвращать нечего?

Операция Enumerable.Last, например, бросает ошибку, если в множестве нет элементов.
это не отвечает на мой вопрос. Enumerable.Last не может вернуть пустой объект нужного типа (не у всех типов есть понятие нуля), в то время как getLastId может при наличии определения нуля (то есть при однозначности значения, которое идёт до первого). Дайте ответ на вопрос, пожалуйста, не хочу додумывать его за Вас и порождать лишнюю причину для несогласия (когда Вы потом скажете «это не я сказал, а Вы придумали»).
название метода было PerformTheContract?

Нет.


Если нет, то почему Вы в методе решаете эту задачу.

Потому что каждый метод должен удовлетворять своему контракту.


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

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


Так всё же этот источник говорит правду или врёт?

Источник говорит правду.


Следуя его рекомендациям нужно ли выбросить эксепшин из метода getLastError, если ошибок ещё не было и возвращать нечего?

Название метода позволяет и ту, и другую трактовку. Поэтому можно сделать и так, и так.


Собственно, именно потому, что поведение неоднозначно, я и избегаю таких деклараций. Error? LastError — сразу понятно, что не бросит эксепшна. Error! LastError — сразу понятно, что бросит. Оба варианта при этом валидны.


Enumerable.Last не может вернуть пустой объект нужного типа (не у всех типов есть понятие нуля),

В C# у всех типов есть понятие "пустого объекта". default(T).


в то время как getLastId может при наличии определения нуля (то есть при однозначности значения, которое идёт до первого)

Угу. Рассмотрим, значит, случай, когда Id — это GUID....


Дайте ответ на вопрос, пожалуйста

А на ваш вопрос нет однозначного ответа. Я бы бросал эксепшн, потому что это исключает ситуацию, когда мы взяли "типа последний id" и передали его дальше как валидный.

Задача кода — уведомить вызывающего, что запрошенная им операция не была выполнена. Эта задача прекрасно выполняется брошенным эксепшном.
То есть код звался WarnAboutFailedOperation? Если нет, то почему название Вашего метода не соответствует решаемой задаче?

Название метода позволяет и ту, и другую трактовку. Поэтому можно сделать и так, и так.
там не написано GetLastErrorOrNoOne. Никаких других трактовок у GetLastError нет. А то, что Вы предлагаете метод называть как переменную (начиная с существительного, а не глагола) — Вы просто нарушаете рекомендации по именованию методов. Если Вы предложили заменить метод на свойство объекта (я просто не знаком с синтаксисом), то это уклонение от вопроса.

А на ваш вопрос нет однозначного ответа. Я бы бросал эксепшн, потому что это исключает ситуацию, когда мы взяли «типа последний id» и передали его дальше как валидный.
И Вы бросали бы эксепшин в методе GetSubstringOffset (название выдумано, не знаю как в C# для этого функция зовётся), если подстрока в строке не найдена? Или Вы поступили бы так, как принято обычно (например в паскале и многих других языках), и возвращали бы -1? Если тут Вы не бросали бы исключение, то поясните чем эта ситуация отличается от GetLastId.

P.S. Это Вы дизлайкаете мои ответы? Если да — могу перестать отвечать, если они Вас так раздражают )
То есть код звался WarnAboutFailedOperation?

Код никак не зовется. Называется метод. Метод назывался SendMessage.


Если нет, то почему название Вашего метода не соответствует решаемой задаче?

Потому же, почему в методе не пишут CheckArguments, CompleteSuccessfully и так далее — есть соглашения об обязанностях, зафиксированные в гайдлайнах (и я их цитировал).


там не написано GetLastErrorOrNoOne.

… а я специально приводил типы, на которые вы даже не посмотрели.


Error? GetLastError() обозначает, дословно, GetLastErrorOrNone(). Error! GetLastError() обозначает GetLastErrorOrFail(). Типы вообще информативны, если вы умеете ими пользоваться.


Если Вы предложили заменить метод на свойство объекта (я просто не знаком с синтаксисом), то это уклонение от вопроса.

… нет, потому что ничего не меняется. Выше привел то же самое в виде метода.


И Вы бросали бы эксепшин в методе GetSubstringOffset (название выдумано, не знаю как в C# для этого функция зовётся), если подстрока в строке не найдена?

Возможно.


Или Вы поступили бы так, как принято обычно (например в паскале и многих других языках), и возвращали бы -1?

Возможно, нет. Не хочу получить InvalidIndex, засунув результат GetOffset в индексер.


Если тут Вы не бросали бы исключение, то поясните чем эта ситуация отличается от GetLastId.

Тем, что семантика разная.

Потому же, почему в методе не пишут CheckArguments, CompleteSuccessfully и так далее — есть соглашения об обязанностях, зафиксированные в гайдлайнах (и я их цитировал).
потому что это не является задачей метода. Его задача не «удачно завершится» и не «проверить аргументы». Его задача — выполнить то, что написано в названии. Если наименование кода не охватывает его назначение в полной мере — это плохо названный код.

P.S. Ответьте пожалуйста на вопрос, это Вы дизлайкаете мои ответы Вам или не Вы? )
Его задача не «удачно завершится» и не «проверить аргументы». Его задача — выполнить то, что написано в названии.

Вот именно поэтому метод называется SendMessage. Задача этого метода — отправить сообщение.

Вот именно поэтому метод называется SendMessage. Задача этого метода — отправить сообщение.
значит таки задача в отправке сообщения, а не в выбросе исключения, если письмо не отправлено? То есть выброс эксепшина не является частью задачи! Исключение — это не часть плана, это вне плана.
То есть выброс эксепшина не является частью задачи!

Частью задачи метода — нет. Кода — да.


Исключение — это не часть плана, это вне плана.

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


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

Приведите хоть один пример

void SendMessage(string text)
{
  //...
  if (cannotSend)
    throw new InvalidOperationException(reason);
  //...
}
Подтвердите, что уважаемые цитируемые источники это утверждают, потому что сейчас Вы это доказываете через свои догадки.

О, нашел вам "более авторитетный" источник, нежели публичный гайданс от MS.


When an action member cannot complete its task, the member should throw an exception.

Jeffrey Richter, CLR via C#, Fourth Edition, Microsoft Press, 2012. Глава 20, "Exceptions and State Management"


Там же:


Many developers incorrectly [выделение мое] believe that an exception is related to how frequently something happens. For example, a developer designing a file Read method is likely to say the following: “When reading from a file, you will eventually reach the end of its data. Because reaching the end will always happen, I’ll design my Read method so that it reports the end by returning a special value; I won’t have it throw an exception.” The problem with this statement is that it is being made by the developer designing the Read method, not by the developer calling the Read method.

Далее, еще раз:


When implementing your own methods, you should throw an exception when the method cannot complete its task as indicated by its name.

Так достаточно?

so that it reports the end by returning a special value; I won’t have it throw an exception
поясните идею, я что-то не понял. Они предлагают наоборот бросать эксепшин? То есть как только я читаю последний байт файла, то генерируется эксепшин, который мне надо ловить, иначе я не смогу продолжить выполнение своего кода? :) С потоковыми сокетами они также предлагают поступать, что когда я прочитал всё, что мне прислали, то не сокет должен изменить статус состояния, сообщив мне, что там пока пусто, а он должен мне эксепшин выдать?

P.S. Я полагаю они тут были неточны, и имели ввиду, что нужно бросать эксепшин в случае, если кто-то попытался продолжить чтение после того, как достиг конца файла — тут я вполне согласен. Просто тогда утверждение о том, что такое бывает редко, является неверным, если я могу как-то обнаружить тот факт, что прочитал файл до конца. Тогда получая порцию данных я просто буду смотреть осталось ли что-то ещё, и если осталось — продолжать чтение, а если нет — прекращу его. Если же я его по какой-то причине не прекратил, тогда эксепшин вполне уместен, почему бы и нет :)
Они предлагают наоборот бросать эксепшин?

Да.


То есть как только я читаю последний байт файла, то генерируется эксепшин, который мне надо ловить, иначе я не смогу продолжить выполнение своего кода?

Нет. Когда вы пытаетесь прочитать байт следующий за последним.


Я так понимаю, остальные цитаты вы решили проигнорировать?

Я так понимаю, остальные цитаты вы решили проигнорировать?
не, меня просто эта зацепила сильно, и остальные надо смотреть в контексте, я их вот так в виде цитат не понял. (уже завтра, видимо, час ночи уже)

Нет. Когда вы пытаетесь прочитать байт следующий за последним.
тогда я не понимаю почему они говорят, что такое бывает часто? Это то, чего не должно происходить. Это то же самое, что попытаться получить 11-ый элемент массива, в котором 10 элементов. Это бывает не часто, а наоборот это то, чего никогда происходить не должно, и тут исключение уместно. Это не противоречит моим словам (путаница появляется только с тем, что они недопустимое поведение называют часто возникающим)
я их вот так в виде цитат не понял

То есть фраза "When implementing your own methods, you should throw an exception when the method cannot complete its task as indicated by its name" вам непонятна?


тогда я не понимаю почему они говорят, что такое бывает часто?

Кто "они"? Джеффри Рихтер (это он, а не "они") такого не говорит.

Because reaching the end will always happen
Джеффри Рихтер (это он, а не «они») такого не говорит.
таки говорит. То есть он ссылается на то, что хоть это происходит часто, но тут надо исключение. но на самом деле это то, что не должно происходить (то есть если оно происходит часто — это ошибка в коде)

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

Прочитайте внимательно. Эта фраза вымышленного программиста.


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

О, уже "это ошибка — нужно исключение".


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


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

Как это далеко ушло от оригинального «любой код должен вызываться по контракту, и если обнаруживается, что контракт был нарушен [...], то код должен бросить исключение, но ни в каких других случаях исключения не уместны.»
не понял. в чём далеко? )) Контракт был нарушен, потому что по контракту должно было быть обеспечено некоторое окружение, в котором код может выполнить свою задачу. Ему окружение не предоставили — он задачу не выполнил. Это ошибка вызова. То, что Вы себе придумали противоречие там, где его нет — это строго Ваше решение.
Так что это РОВНО ТО о чём я и говорил изначально.

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

В этом случае, коды ошибок не нужны никогда (кроме как внутри исключений).
коды ошибок не нужны. Давайте внесём такое определение «ошибка» — это поведение системы отличное от прогнозируемого при её создании.
То есть если при создании программы мы рассчитывали, что программа будет всегда отдавать по http статус 200, а она отдала 404 — это ошибка. Если мы прогнозировали, что она по http будет отдавать 200, 3xx и 404 — то это всё статусы, которые мы будем передавать выше просто как результат выполнения операции. Но при этом статусы 400 и 5xx мы снова же будем считать ошибками и кидать исключения, если при создании программы мы рассчитывали, что такие статусы возникать не будут.

Тут надо понимать, что слово «ошибка» может иметь разные значения, и иногда люди используют его в отношении отрицательных статусов (ну то есть они ожидают в ответ статус http 404, но при этом именуют его ERR_NOT_FOUND просто потому, что «это же не статус успешного выполнения операции, а значит явно ошибка»)
коды ошибок не нужны.

А теперь давайте сопоставим это с вашим же высказыванием:


"Но если такие ситуации были предусмотрены по контракту, то тут нет причины бросать исключение и вполне логично вернуть код ошибки".


Давайте внесём такое определение «ошибка» — это поведение системы отличное от прогнозируемого при её создании.

Я с этим определением не согласен. Пример см. выше про сервис, который отвечает в доле случаев. Это прогнозируемое поведение.

А теперь давайте сопоставим это с вашим же высказыванием:
я уже говорил как так получается. Слово «ошибка» понимается в двух разных значениях. В одном случае в том значении, с которым Вы не согласны, а в другом — отрицательный статус выполнения операции. В первом случае только эксепшины, во втором случае — возврат статуса. Но так как второе значение более широкое, чем первое и по сути полностью в себе первое умещает, то тут важно понимать, что многие ситуации подпадают под оба определения, и в этом случае надо идти по первому пути — бросать эксепшин.
Слово «ошибка» понимается в двух разных значениях.

Не надо так. Будет путаница.

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

вот определение ошибки в кэмбридском словаре в разделе IT
a situation in which something goes wrong in a computer program, or a message that this has happened:
То есть когда в книгах пишут error, то вероятнее всего имеют ввиду именно это. «ситуация, в которой что-то идёт не так (по неверному пути) в компьютерной программе или сообщение об этом.» Когда мы получаем тот результат, который предполагали получить, то это не называет «не так» или «по неверному пути». То есть wrong way — это то, что произойти не должно. А это именно тот случай, в котором и надо бросать эксепшин по моему мнению. Вы же желаете понимать под этим словом другое — некоторый вполне ожидаемый результат, когда всё идёт по плану, и код получил отрицательный ответ, который в принципе и планировал иногда получать :) Это никакое не wrong way ))
Я то что могу сделать?

Не использовать двусмысленную терминологию.


То есть когда в книгах пишут error, то вероятнее всего имеют ввиду именно это.

Не обязательно. Еще могут иметь в виду общебытовое значение слова "error".


«ситуация, в которой что-то идёт не так (по неверному пути) в компьютерной программе или сообщение об этом.»

Никакого "неверного пути". Его там нет. Просто "что-то идет не так".


Когда мы получаем тот результат, который предполагали получить, то это не называет «не так» или «по неверному пути».

Кто "мы"? Пользователь? Пользователь не предполагал, что он введет логин, а вместо регистрации получит сообщение "логин занят". Для него "логин занят" — это "something went wrong, I cannot register". Ошибка. При этом для системы это совершенно рутинная, ежедневная ситуация.


То есть wrong way

А где вы взяли "wrong way"? В вашей цитате его нет.

Не обязательно. Еще могут иметь в виду общебытовое значение слова «error».
В технической литературе скорее всего имеют ввиду техническое значение термина. Я не говорю, что точно имеют ввиду, но если нет возможности это определить, то имеет смысл считать, что термин используется в соответствии со значением в соответствующем словаре.

Никакого «неверного пути». Его там нет. Просто «что-то идет не так»
хорошо, если убираем моё уточнение, то тогда следует лучше подобрать слова: «происходит что-то ошибочное/неверное».

Кто «мы»?
наш код. То есть мы в лице кода, который написали, чтобы он делал что-то вместо нас.

Пользователь?
нет, если только мы не автоматизируем работу пользователя. Тут речь про того, чьи действия заменяются кодом.

А где вы взяли «wrong way»? В вашей цитате его нет.
да, Вы правы, перевёл свою же фразу обратно, похоже. Ладно, эта часть для меня не существенна. Меня просто устроит замена «происходит не так» на «происходит что-то ошибочное/неверное», что является достаточно точный выражением смысла текста.
тогда следует лучше подобрать слова: «происходит что-то ошибочное/неверное».

Ошибка — происходит что-то ошибочное. Гм.


наш код. То есть мы в лице кода, который написали, чтобы он делал что-то вместо нас.

Так вот. Если мы хотим сохранить файл, но запись в выбранное пользователем место невозможна — это something goes wrong. Оно wrong, потому что мы не можем сохранить файл. Так что это совершенно точно ошибка, строго по вашему определению, "не смогли сохранить файл".


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

Ошибка — происходит что-то ошибочное. Гм.
убираем «ошибочное» и оставляем «неверное/неправильное» (два других перевода слова wrong).

Но при этом мы можем предполагать, что такой результат возможен, и дать пользователю возможность повторить операцию, или выбрать другое место, или еще что-нибудь.
если для кода, который пытался сохранить и не смог, эта ситуация описывается как «something goes wrong», то в контексте этого кода утверждение «Но при этом мы можем предполагать, что такой результат возможен» является неверным. То есть в этом контексте ситуация произойти не должна была. Но парочкой уровней выше вполне может быть код, задача которого — если файл не сохранился, то попробовать повторить операцию ещё 2 раза и сообщить результат. И этот код уже эксепшинов бросать не будет (и зваться будет не saveFile, а trySaveFile с параметром numberTries), потому что он уже предполагает, что файл может не сохраниться :)
в контексте этого кода утверждение «Но при этом мы можем предполагать, что такой результат возможен» является неверным.

Почему? Какое формальное общепринятое правило об этом говорит?

Почему? Какое формальное общепринятое правило об этом говорит?
если Вы под «возможно» имеете ввиду «возможно что угодно», то да. Но в таком значении оно теряет смысл, потому что не соответствует критерию Поппера. Если же считать под «возможно» то, что должно иногда происходить, то оно не может называться «goes wrong», потому что неправильным было бы наоборот, если бы никогда не происходило то, что должно иногда происходить :) А вот иногда происходящее то, что и должно иногда происходить — это не wrong, а right ))
Если же считать под «возможно» то, что должно иногда происходить,

Нет. "Возможно" — это то, что может происходить, но не обязательно должно происходить.

Вот вам еще пример, тоже в терминах "должно быть" и "не должно быть".


Когда я пишу код:


char First(string val) => val[0]

Я задумываюсь: а может ли val быть null. Если такой ситуации быть не должно (например, проверка на null есть выше, или у меня вообще код с явным nullability), то я так код и оставлю, зачем его лишний раз трогать.


Но вот если такая ситуация возможна, то я код поправлю:


char First(string val) =>
  val != null
  ? val[0]
  : throw new ArgumentNullException(nameof(val))

… потому что я хочу нормальную диагностируемую ошибку.


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

В Ваш блок должна переменная val передаваться как null? То есть это вариант правильных входных данных и окружения, которые предусмотрены в коде? Или это тот вариант данных, при котором код будет вести себя непредсказуемым или гарантированно неверным образом? Если это правильные входные данные — эксепшину не место. Если неправильные — место. Если Ваш уровень кода эту переменную не контролирует, а просто делегирует работу с ней на уровень ниже — эксепшин не нужен, так как это ответственность более низкоуровневого кода. не нужно заниматься микроменеджментом и пытаться отслеживать каждую мелочь, нужно отслеживать всё, что важно для Вашего масштаба выполнения задачи. И тут дело совсем не в том бросит ли более низкоуровневый код свой эксепшин или не бросит, тут важно то насколько Вы за это ответственны. Например высокоуровневый код не должен заботиться о правильности адресов данных в памяти, он должен заботиться о самих данных. Потому если на вашем уровне кода Вы работаете с изображением в памяти, то надо бросить эксепшин если Вам прислали неправильные координаты пикселя, но не надо пытаться отслеживать ситуацию выхода за пределы выделенной под картинку памяти — это забора другого кода, который обслуживает работу с памятью.
В Ваш блок должна переменная val передаваться как null?

Что значит "должна"? Я, кажется, достаточно точно описал ситуацию. Нет, "не должна". Но может (или не может, если я знаю, откуда меня вызывают).


Если Ваш уровень кода эту переменную не контролирует, а просто делегирует работу с ней на уровень ниже — эксепшин не нужен, так как это ответственность более низкоуровневого кода.

Про диагностику вы, похоже, не прочитали. Понятно.


Вам нравится ловить и диагностировать NullReferenceException, брошенный из строчки context.User.Identity.Claims.Name? Мне — не нравится.

или не может, если я знаю, откуда меня вызывают
то есть инкапсуляции 0? Когда Вы пишите код, то задумываетесь о том, откуда его будут вызывать и учитываете особенности кода, к которому Ваш не имеет никакого отношения? Сам Ваш код что предполагает? Для него val = null — это нормальное значение, или это то, что в него попадать не должно (по смыслу переменной там должен быть не null)? Если это нормальное значение — обрабатывайте его, код на это рассчитан. Если не нормальное — бросайте исключение, Вам обещано другое (Вы свой код писали не планируя такие данные получать)

Мне — не нравится
Вопрос не в «нравится или нет», а в том почему ошибка возникла. Если Вы работаете со строкой, то проверьте её содержание, если к нему есть какие-то требования. Но не надо проверять то, за что не ответственны. Например если типы данных контролируются фреймворком — оставьте это ему. Если же это задача Вашего уровня кода — конечно ловите ошибки сами и бросайте эксепшины. Ну это как с менеджментом, как я уже говорил. Ген.директор может стоять у программиста над головой и указывать на допущенные опечатки и ошибки в коде, а может сидеть и заниматься тем, чем ему положено — управлением компании. И вот когда он занимается управлением компании, то она с большей вероятностью процветает, чем когда он стоит над головой у программиста и «помогает». Так и Ваш код, который помогает фреймворку с его задачей вместо выполнения собственных, делает слегка не то, что нужно.
то есть инкапсуляции 0?

Нет. Вы про концепцию баррикад не слышали? Code Complete, 8.5:


The class's public methods assume the data is unsafe, and they are responsible for checking the data and sanitizing it. Once the data has been accepted by the class's public methods, the class's private methods can assume the data is safe.

.


Вы пишите код, то задумываетесь о том, откуда его будут вызывать

Да, конечно.


Если не нормальное — бросайте исключение, Вам обещано другое

Нет, не нормальное. Но если я буду вставлять проверку на такие "ненормальные" значения в каждом методе, код распухнет до невозможности. Code Complete, 8.8:


Too much defensive programming creates problems of its own. If you check data passed as parameters in every conceivable way in every conceivable place, your program will be fat and slow. What's worse, the additional code needed for defensive programming adds complexity to the software.

.


Вопрос не в «нравится или нет», а в том почему ошибка возникла.

Она возникла потому, что где-то в вызывающем коде кто-то передал null.


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

Вы все еще не поняли про диагностику. Я не "помогаю фреймворку". Я помогаю себе найти место, где случилась ошибка в проде.

Нет. Вы про концепцию баррикад не слышали?
само название не слышал, а идея очевидна. Но тут речь не про то «откуда он будет вызван», а про специфику кода. Это метод приватный, и этим он принципиально отличается от публичного. В данном подходе приватный метод не должен вообще бросать эксепшин в связи с ошибкой входных данных, потому что проверка входных данных находится не в его зоне ответственности. Таким образом мой вопрос касается только публичных методов. Итак val = null на входе в публичный метод является правильным значением или неправильным? Ну например, если val — это строка, для которой нужно посчитать какой-то хэш, то val = null является неверным значением, потому что Вы собираетесь считать только хэш строки. Так вот в Вашем случае val = null является верным значением на входе?

Да, конечно.
исходя из сказанного Вами выше — нет :) Не знаю зачем Вы говорите «да», если на самом деле Вы пишите разное поведение для методов основываясь на их собственных особенностях. И просто договорились, что приватные методы данные не проверяют (не ответственны за их верность).

Но если я буду вставлять проверку на такие «ненормальные» значения в каждом методе, код распухнет до невозможности.
так и не надо в каждом методе. Определите того, кто за это ответственен и проверяйте там. Положите слой валидаторов между внешними источниками данных и кодом, который с ними работает. Если накатывает паранойя, то можно ещё в несколько самых критичных мест добавить, где вот ну совсем нельзя, чтобы что-то пошло не по плану. Но зачастую это излишне, и при наличии слоя валидаторов сам код обычно проверяет только окружение (ну или точнее реагирует, если неправильное окружение как-то мешает правильно выполнить задачу).

Too much defensive programming creates problems of its own. If you check data passed as parameters in every conceivable way in every conceivable place, your program will be fat and slow. What's worse, the additional code needed for defensive programming adds complexity to the software.
да, об этом и говорю. Входящие извне данные проверяются валидаторами («извне» тут определяется программистом, он по своему усмотрению определяет области безопасных данных), а сам код уже реагирует только на ошибки окружения, которые возникают/обнаруживаются в ходе выполнения задачи.

Она возникла потому, что где-то в вызывающем коде кто-то передал null.
))) я догадался. Вопрос был о сути причины, а не об очевидной констатации факта в следствии которого в метод попадают неверные данные. Это как на вопрос «почему солнце светится» ответить «потому что оно излучает свет» — я бы сам не догадался )))

Я помогаю себе найти место, где случилась ошибка в проде.
а чем Вас не устраивает системное исключение? у него нет стека вызовов? Почему Ваш код высокого уровня ловит ошибки произошедшие на низком уровне, и спровоцированные более высоким уровнем? То есть к этой ошибке отношение имеют все, кроме того кода, в котором Вы эту ошибку решаете ловить :)
само название не слышал

… и при этом ссылаетесь на Code Complete как на авторитетный источник? Занятно, занятно.


Но тут речь не про то «откуда он будет вызван», а про специфику кода. Это метод приватный, и этим он принципиально отличается от публичного.

Да нет, речь именно о том, откуда будет вызван. Потому что "забарикадированная" зона не обязательно ограничена одним классом.


Так вот в Вашем случае val = null является верным значением на входе?

Я же специально привел код. Могу еще раз, мне не сложно:


char First(string val) => val[0]

Я вам сразу написал, что этот код, если передать на вход null, бросит эксепшн. Это не обсуждается, это факт такой. val = null — это правильное значение на входе?


исходя из сказанного Вами выше — нет

… и это все, что нужно знать о том, как вы объективно пользуетесь законами логики применительно к естественным языкам.


Не знаю зачем Вы говорите «да», если на самом деле Вы пишите разное поведение для методов основываясь на их собственных особенностях.

Я говорю "да", потому что я думаю о том, откуда и как будут вызываться объекты, которые я пишу. Не "об их особенностях", а о том, как они будут использоваться.


Определите того, кто за это ответственен и проверяйте там.

… я же вроде дал ссылку на соответствующую главу, зачем вы мне теперь ее пересказываете?


Вопрос был о сути причины, а не об очевидной констатации факта в следствии которого в метод попадают неверные данные.

Откуда ж я знаю? Это код, он упал, в логе ошибка, надо разбираться.


а чем Вас не устраивает системное исключение?

Тем, что NRE, брошенное из строки context.User.Identity.Claims.Name, может иметь четыре потенциальных причины, но в стектрейсе будет в лучшем случае номер строки (один на все "причины"), а в худшем — название метода, где таких строк еще и больше одной.


Почему Ваш код высокого уровня ловит ошибки произошедшие на низком уровне

Он этого не делает, вы это придумали зачем-то.

Исключения это интересная тема. Если где-то в недрах кода (возможно даже в сторонней библиотеке) возникла недопустимая ситуация — например невозможность выделить память — то нужно предпринять какие-то действия. По идее, это может быть и код ошибки, и исключение, и даже аварийное завершение работы всей программы. Но что именно?

В какой-то момент мне пришла в голову мысль, что в языке программирования должна существовать некоторая конструкция, определяющая «ошибочную ситуацию». Нечто среднее между «return кода_ошибки» и «throw исключения». То есть библиотека говорит «у меня произошла ошибка», а как именно она должна быть обработана — это вопрос вызывающего кода. Ведь даже обработка исключений может быть разной (SEH, DWARF, SJLJ). В конце концов, код возврата — это тоже способ организации исключений, особенно если компилятор возьмет на себя часть этой работы (очевидно что для этого потребуется сказать компилятору какие коды означают ошибки и обеспечить тип возврата всех функций, участвующих в этой системе).

То что любая функция может внезапно выкинуть любое исключение (как в С++) — это плохо и по сути является аналогом goto. Реализация в Java (с явным указанием исключений которые могут быть выброшены) лучше (именно тем что там исключения указываются явно). Но можно пойти еще дальше.

В некотором роде исключения — это модуль языковой функциональности (как RTTI в C++, отключаемую опцией компиляции). В определенных случаях (например программирование под микроконтроллеры с малым объемом памяти) исключения целесообразно отключать вообще, переводя весь код на самую простую реализацию — кодами возврата. При этом хотелось бы, чтобы библиотеки были общие (т.е. не держать отдельные наборы библиотек для каждой реализации исключений).
То что любая функция может внезапно выкинуть любое исключение

Почему бы не ловить все исключения?
Реализация в Java (с явным указанием исключений которые могут быть выброшены) лучше (именно тем что там исключения указываются явно).

Не все с этим согласны
В какой-то момент мне пришла в голову мысль, что в языке программирования должна существовать некоторая конструкция, определяющая «ошибочную ситуацию». Нечто среднее между «return кода_ошибки» и «throw исключения».

в с++ есть std::error_code.
«То, чего никогда не может быть» — это только необработанное исключение. Было бы странно вводить в большинство современных языков довольно сложный механизм try\catch\throw для случаев, которые вообще никогда не должны выполняться, не находите?

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

В статье ни одного аргументированного довода против исключений не нашел. Всё сводится к тому, что это «сложно» или «не нравится». Сложность вижу только в том случае, если нет порядка и половина функций возвращает значения-ошибки, а половина кидает исключения. Если принять за правило, что все функции работают унифицированно — не запутаешься, и через два года не нужно будет ничего вспоминать.

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

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

Вы не сможете смело использовать уже написанные методы в других местах, ведь они могут порождать неожиданные исключения;

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

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

С исключениями тоже так может быть — пустой catch/except блок.
«То, чего никогда не может быть» — хорошо, перефразирую. «то, чего никогда не должно быть».
В более широком смысле исключение описывает ситуацию, в результате которой локальный кусок кода не может дальше выполняться

очень часто можно встретить выход в середине метода, потому что дальше он не может выполняться. Например когда происходит валидация массива значений, но сразу же обнаруживается, что там не массив, или когда был введён адрес АПИ к которому нужно было сделать запрос, но сам адрес оказался ошибочным и потому выполнение запроса невозможно и локальный код приходится завершать. Но если такие ситуации были предусмотрены по контракту, то тут нет причины бросать исключение и вполне логично вернуть код ошибки (которых там может быть 100 штук, начиная от недоступности АПИ и завершая ответом от АПИ, что пользователь неправильно ввёл пароль или указал недопустимый возраст).
В статье ни одного аргументированного довода против исключений не нашел

В статье указан основной недостаток — запутанность кода и нарушение инкапсуляции. Мы должны знать не только контракт вызова кода (функции), но и учитывать его внутренние особенности. Знать, что в каких-то вполне штатных ситуациях он может просто оборвать цепочку вызовов внешнего кода. Одно дело когда он обрывает цепочку вызовов потому, что его самого вызвали неправильно (передав неверные параметры или не позаботившись об окружении), а совсем другое дело, когда его и вызвали правильно, и всё в него правильно передали, а он БАЦ и исключение бросил.
А дальше вообще начинаются «Вредные советы»:
Пускай на верхнем уровне висит обработчик исключений и превращает все необработанные исключения в ошибки, сохраняет данные и деликатно завершает выполнение

Вырвали из контекста. Речь была про то, что любое исключение должно быть обработано. То есть исключения нужны не для того, чтобы ронять программу, а для того, чтобы правильно обрабатывать возникшие ошибки. Одним из аргументов того, что клиентский ввод не может обрабатываться как исключение, я видел в интернете утверждение типа, что нельзя ведь взять и прервать (уронить) выполнение программы просто потому, что человек опечатался. Так вот я и говорю, что обработка ситуации посредством порождения исключения не означает, что программу надо уронить. Как минимум на верхнем уровне всегда должен присутствовать обработчик всех необработанных ранее исключений, который будет логировать ошибки, показывать сообщения об ошибках пользователям и т.д. Это было к тому, что бросать исключения или не бросать никак не зависит от источника данных или чего либо ещё. И клиентский ввод может обрабатываться как исключение.
Если вы вернете из функции неизвестный код ошибки, ситуация будет еще опаснее.

Я не могу вернуть неизвестный код ошибки, потому что это будет нарушением контракта. Код точно знает какие ошибки он возвращает и внешний код должен точно знать, какими могут быть результаты выполнения вложенного кода (что он получает в ответ). Но когда код часть возвращаемых значений отдаёт не как возврат, а бросая исключение — это может стать серьёзной проблемой. Насколько я помню я саму статью писал под впечатлением от беседы на форуме, где человек рассказывал насколько круто он реализовал проброс ошибок валидации данных с помощью исключений сразу на несколько уровней и как это упростило его код. Потому я в статье уделял такое внимание именно случаем неуместного использования исключений (значительно больше, чем случаям их уместного использования). То есть человек вызывает функцию валидации для того, чтобы проверить верны ли данные, а в этой функции, обнаружив, что неверны, порождает исключение (как будто бы это что-то невообразимое, что потенциально ошибочные данные оказались ошибочными)
Но если такие ситуации были предусмотрены по контракту, то тут нет причины бросать исключение и вполне логично вернуть код ошибки

Почему? Я вот не вижу никакой формальной логики (кроме вашего желания), которая бы нам говорила, что мы не можем в контракте предусмотреть ситуацию "в такой-то ситуации бросается исключение". Более того, в .NET это общепринятая практика.

Приведите пример хорошего по Вашему мнению контракта предусматривающего исключение, и я Вам продемонстрирую чем это плохо. Пока могу только сказать то, что любой контракт, который предусматривает события, которые не должны произойти, является плохим контрактом (по меньшей мере избыточным). Представьте, что Вы с кем-то договариваетесь о встрече следующим образом: «давай встретимся завтра в кафе в час дня. Я обязательно буду. Но если не буду значит обязательно приди ко мне на похороны, потому что я умер. Но если потухнет Солнце — можешь не приходить...» ну и т.д. Вот зачем все эти исключения предусматривать по контракту, если достаточно сказать «я обязательно буду» что и будет контрактом, а вот все эти исключительные ситуации — это как раз реакция на нарушение контракта, которая очевидно в виде исключения и реализуется.
Приведите пример хорошего по Вашему мнению контракта предусматривающего исключение, и я Вам продемонстрирую чем это плохо.

"Если разработчик запрашивает в словаре несуществующий элемент, он получит исключение "несуществующий ключ""

«Если разработчик запрашивает в словаре несуществующий элемент, он получит исключение „несуществующий ключ“»
В таком виде да, ничего страшного нет. Так исключения в контрактах и описываются: как реакция на некоторые недопустимые действия (в Вашем примере меня предупреждают какие меня ждут последствия, если я запрошу несуществующий/неправильный ключ). Это значит, что я должен быть уверенным в существовании запрашиваемого ключа.

Описание исключений в контракте подобно описанию неустойки в договоре. Его там описывают не с расчётом на «неустойку описали, ну и хорошо, теперь можно нарушать пункты договора, всё предусмотрено», а с расчётом на «другая сторона может не выполнить свои ОБЯЗАТЕЛЬСТВА по договору и тогда мы поступим вот так». То есть неустойка хоть и является частью договора, но применяется тогда, когда его кто-то уже нарушает. Так и исключения, хоть и описываются в контракте, но применяются когда этот контракт уже нарушается.
Я же подумал с Ваших слов, что Вы предлагаете исключение сделать частью сути контракта, вида «функция генерирует число от 1 до 10, но если сгенерированное число больше 5, то функция сгенерирует исключение...», потому и сказал, что это гарантирует какую-то избыточность.
Так исключения в контрактах и описываются: как реакция на некоторые недопустимые действия

Угу, а теперь напомню вашу исходную фразу:


Но если такие ситуации были предусмотрены по контракту, то тут нет причины бросать исключение

Эта ситуация (несуществующий ключ) предусмотрена по контракту, но мы все равно бросаем исключение.


Что и требовалось доказать.


Описание исключений в контракте подобно описанию неустойки в договоре.

Не более, чем дохлая рыба подобна котенку с дверцей.


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

Да… и это не «неустойка взимается когда нарушен договор», а «неустойка взимается, чтобы соблюсти договор» :) Немного смешно прям.

Вы под контрактом понимаете расширительное понятие, в которое включаете все договорённости о действиях при его же нарушении. Это как прочитать в инструкции к устройству «если устройство зависнет — выключите и включите питание» и говорить, что одной из функций устройства является зависание, а одной из функций пользователя устройства — выключение и включение питания. Хотя на самом деле одно является ошибкой, а другое просто реакцией на ошибку… но для Вас если в инструкции что-то написано, значит это часть контракта.
Да… и это не «неустойка взимается когда нарушен договор», а «неустойка взимается, чтобы соблюсти договор» :) Немного смешно прям.

Вот именно поэтому аналогии врут.


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

Нет, я под контрактом понимаю описанное и зафиксированное поведение компонента.

У исключений есть большое преимущество перед возвратом функции. Просто огромнейшее: стектрейс. Неожиданно вывалившееся исключение можно проверить, отдебажить, воспроизвести. Если функции начинают возвращать -1 — найти источник ошибки гораздо сложнее.


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


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

ИМХО главная проблема исключений — нечитаемость кода. Очень сложно найти место, где исключение будет обработано. А ещё бывает, что где-то забыли catch, или написали там не тот конкретный тип исключения. И всё, вроде, работает, и даже сообщение об ошибке выдаётся, но ошибка обрабатывается не так, как было задумано. ИМХО программировать с использованием кодов возврата проще. Как минимум, до какого-то уровня сложности системы.
UFO just landed and posted this here
В Java и С++ не запрещено ловить базовый тип, даже если бросается наследник (что логично). Скажем так: можно сделать нормально, но никто не мешает сделать коряво (что, впрочем, вряд ли является отличительной чертой исключений).
ИМХО программировать с использованием кодов возврата проще.

Вот только проигнорировать код возврата (во многих языках) проще, чем исключение.

исключения как инструмент совершенно не уникальны в том, что при корявом их применении код становится сложным. Например, как только начинаешь использовать коды возврата, все функции по умолчанию начинают возвращать не bool/string/..., а код ошибки. И в конечном итоге оказывается, что при возникновении какой-то ошибки её код возврата тянется через всю программу, а обрабатываются они точно так же в ограниченном количестве мест. Архитектура получается точно такая же, как и с исключениями, но с исключениями проще писать как позитивный, так и негативный сценарий выполнения.

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

Это если оно unhandled и на самом деле исключение.
Извините чукча не читатель. Плохой учитель губит хороших учеников.
«Все исключения могут быть продиктованы только заданием или здравым смыслом», о боже откуда это? А как же формализация?
Исключения должны выбрасываться в том случае, если нарушаются одно из трех условий при вызове члена типа.
Предусловие, инвариант, пост условие. Как это разумно и правильно применить в контекте проекта это уже другой вопрос.

Предусловие — проверяет входные параметры и инвариант типа. Потом происходит выполнение каких-то операций, которые могут нарушать инвариант. Перед возвращением значения происходит проверка постусловий и инварианта.
Это если в кратце.
Не успел отредактировать.
Предусловия это условия при которых метод не сможет выполнить возложенную на него обязанность, обычно в предусловиях проверяют валидность входных данных.
Пост условия это проверка условий, что метод выполнил корректно свою обязанность. Инвариант это условия которые должны выполняться всегда, исключение составляют методы в которых этот инвариант на время выполнения может быть нарушен.
Например есть тип Employee, c методами IncreaseBonus и DecreaseBonus.
Пусть будет объект employee с с бонусом равным 10. И кто-то например вызывает employee.DecreaseBonus(20), методы не имеют возвращаемого значения. Предусловие проверяет что 20 > 0, инваринат проверяет что текущее значение бонуса больше 0, 10>0.
Потом происходит вычисление и бонус становится -10. Проверки на пост условие нет, хотя теоретически это может быть успешность записи еще куда либо, а инвариант проверят что бонус отрицательный и выбрасывает исключение. И объект находится в неконсистетном состоянии, при вызове любого другого метода инвариант должен выбрасывать исключение.
Данный пример с точки зрения дизайна не является лучшим, но наглядно демонстрирует условия.
В реальности примером инварианта в C# является правильная реализация интерфейса IDisposable, где перед каждым методом проверяет флаг isDisposed, другим примером может быть проверка isConnected при вызове в типах которые оперируют связью. Примеры предусловий это проверка аргументов на валидность значений.
По поводу проверки и бросания исключений от пользовательского ввода, тут все определяется контрактом. Если модель не может работать с пустой строкой, то если значение передаваемого параметра пустая строка — исключение однозначно, чтобы исключения не возникало, об этом должен позаботиться UI — не давать применить такое значение.
Вы также не затронули тему о иерархии исключений и их обработке, но этого вообще хватит на отдельный пост.
Учите мат часть она давно уже написана.
Вы не сможете смело использовать уже написанные методы в других местах, ведь они могут порождать неожиданные исключения;

А что такое «неожиданное исключение» и почему оно является препятствием? По сути, это неожиданный код возврата. Функция возвращает вам, скажем, длину буфера, или -1 в случае неудачи. А вы не знали, что значение может быть отрицательным — это неожиданный для вас результат. Значит ли это, что код возврата нельзя использовать? Нет. С исключениями то же самое — выбрасываемые в виде кода ошибки исключения должны быть документированы, так же как и возвращаемые функцией значения.

GOTO зло — потому что путает код, передавая управление в произвольную точку. Не следует думать, что передача управления, перепрыгивая произвольное количество вызовов в стеке, меньше путает ваш код, чем GOTO;

Ничего подобного. Если так рассуждать, то мы должны отказаться от конструкций:
for ()...{
break;
}

if (){

} else {

}

В первом случае у нас есть неявный GOTO вовне цикла, во втором — неявный GOTO к блоку ELSE.

GOTO — не must die. Must die неправильное его использование. Самая большая проблема с goto — это прыжок назад по коду с созданием петли. Именно это тяжело отслежить и понять, именно из-за этого получается пресловутое спагетти. Исключение не бросает нас вверх по ходу исполнения, оно бросает нас вниз, дальше.

Не следует считать, что через 2 года вы влёт вспомните, что этот модуль системы для отрицательных ответов использует исключения

Такой же странный аргумент, как и первый. И тот же самый контр-аргумент. Вот вы надеетесь на то, что код ошибки — это -1. А потом добавили еще -2 и -3. Через поименованные константы, разумеется. А результат функции по-прежнему проверяете на == -1. И? Какая разница? Кто мешает вам ловить все коды ошибки меньше нуля или все исключения (определенные + «остальное»)?

Использовать нужно то, что сделает ваш код читаемым и надежным. Лучше коды ошибок? Пожалуйста. Лучше исключения? На здоровье. Лучше goto с переходом в блок деинициализации в конце функции? Да рали бога.
В разработке руководствуюсь правилом: «исключения для программистов, а не для пользователей.»
То есть если пользователь забыл ввести значение в поле формы — это просто ошибка, не исключение.
А вот если где-то «внутри» очень важная переменная вдруг оказалась не заданной — нужно кидать исключение, причем вывести и имя функции, и сообщение, и значения других параметров, которые помогут найти эту ошибку.
>GOTO зло — потому что путает код, передавая управление в произвольную точку.

В PHP не в произвольную, есть разумные ограничения.

>Вот теперь заказчик сам вам сказал, где следует породить исключение в написанной для него программе

Не в этих ли случаях их использовали, давая указанные вами пояснения? :)
Удивительно, но в статье ничего не сказано о том, что исключения ломают referential transparency. И это тот самый пункт номер 1 с которого надо начинать, когда мы говорим вообще что-либо об исключениях. А ещё они также не composable + не работают в multi-threaded окружении.

Referential transparency — это хорошая такая штука, особенно в функциональном программировании, но вот только не всегда достижимая. Один раз у вас память выделилась, другой раз — нет.


А ещё они [...] не работают в multi-threaded окружении.

Работают. Просто надо уметь их готовить.

А есть подходы, которые не надо уметь как-то специально готовить и которые просто работают, при этом всегда и корректно… А в моменты когда они не работают корректно, программа просто не компилируется ;-)

Вместо ссылки на часовое видео, можно было просто сказать "монада Try". Так вот, у меня для вас плохие новости.


(а) монада Try не решает проблему referential transparency. Если внутри функции, пусть даже она возвращает Try[T], есть побочный эффект (например, работа с бд), функция не будет чистой, и, как следствие, referential transparency будет нарушена. Да фиг с ней, с БД, банальное выделение памяти — и ваша функция уже недетерминирована.


(б) монада Try не решает проблему обработки ошибок в многопоточности: если вы положите функцию, возвращающую Try[T], в другой поток, и проигнорируете ее результат — как вы и делаете в другом комментарии, — вы ничего не узнаете о случившихся ошибках.


(ц) в обоих этих случаях компилятор вам никак не поможет. Более того, прямо скажем, есть еще сильно больше одного способа "сломать" Try (впрочем, как и любой case class) даже при полной поддержке компилятором — и это еще не упоминая того факта, что описанная вами поддержка есть далеко не везде (скажем, в F# на необработанный кейс будет предупреждение, но код скомпилируется, в Scala, афаик, так же).

>не работают в multi-threaded окружении

1. PHP, Node.JS — однопоточные. :) Нам можно :)
2. А если войти в критическую секцию, то, кмк, все должно работать. Но сложновато. Как и любой другой многопоточный код. :)
не работают в multi-threaded окружении.

Это с какой стати? Вас не затруднит пояснить развернуто? Без каких-либо проблем используем исключения в многопоточных системах.
try {
  Task(some_work).run() // (1)
catch {
  // (2) never reach this point
}


В примере выше, если Task запускает some_work в другом треде и в этом треде some_work породит исключение мы по дефолту в точке (2) его не поймаем никогда, если только не напишем специальную обвязку вручную, для проброса исключений из слейв-треда в мастер-тред.

Ну так вы же проигнорировали результат Task.Run — так что неудивительно, что вы не получили информации о ходе выполнения. В таком коде, прямо скажем, some_work может никогда и не выполниться.

Это просто схемотичный пример, а не кусок кода, который должен компилироваться. В интернете погуглите пожулуйста «Referential transparency» и найдёте много информации почему исключения её ломают и, самое главное, как этого можно избежать с примерами кода и т. п.

Я знаю, почему исключения ломают referential transparency, но мы-то здесь не о referential transparency, а о многопоточности.


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

Я имел ввиду следующее. Сейчас попробую расписать подробнее. Допустим у вас есть поток B в котором происходит какая-то полезная работа. В какой-то момент в этом потоке B происходит ошибка и выбрасывается исключение. Это исключение может быть перехвачено и обработано только внутри потока B. Как поток A, который расчитывает, что от потока B рано или поздно придёт какой-то результат, узнает, что в B вычисление завершилось неудачей? По умолчанию никак. Но можно написать обвязку, которая будет прокидывать ошибку из потока B в поток A (тем или иным способом) и уже дальше в потоке А, например, кидать исключение. Это будет работать, но для этого придётся написать какой-то код. Ещё раз, исключение брошенное в одном потоке можно поймать и обработать только из этого же потока. По умолчанию другие треды про него ничего не узнают, если не предпринять специальных телодвижений.
Как поток A, который расчитывает, что от потока B рано или поздно придёт какой-то результат, узнает, что в B вычисление завершилось неудачей?

Давайте начнем с простого вопроса: как поток A узнает, что от потока B пришел какой-то результат? По умолчанию это так не работает.


(вау, я только что нашел фундаментальную проблему в операторе return)

Как поток A, который расчитывает, что от потока B рано или поздно придёт какой-то результат, узнает, что в B вычисление завершилось неудачей? По умолчанию никак

Воу-воу-воу, секундочку. Если мы ожидаем результата от потока Б, то мы ожидаем какого-то события, сигнализирующего достижения результата, или окончания потока Б. Т.е. в любом случае у нас будет код, который является прослойкой между двумя этими потоками. Так или иначе. Кто мешает этой прослойке проверять исключения? Например, что-то вроде
WaitForSingleObject(hThreadBEvent)
if lThreadB.Outcome == oFailure 
  CheckException(lThreadB)....
...
...
Thread.HandleException(Exception){
Terminate;
Outcome = oFailure;
SetEvent(hmyEvent)
}

Прямо скажем, .net-овский Task, который местная реализация монады Future, как-то так и делает (не по реализации, а по результирующему поведению):


var task = Task.Run(somework);
//...
await task; // завершится только тогда, когда somework закончен, и если в somework был эксепшн, он будет выкинут здесь
Так, простите, где здесь пример того, что исключения не работают в многопоточном окружении?
Если исключение будет порождено методом run, оно поймается. Исключения, порождаемые some_work'ом, должны ловиться исключением обертки Task, запускающей отдельный поток.
функция, которая может то выполниться, то зафейлиться, в принципе не может быть чистой. Просто при использовании исключений она выглядит чистой, а ошибки и результат выполнения позитивного сценария обрабатываются в разных местах
У непроверяемых исключений есть хороший юз кейс: нефатальные ошибки без возможности восстановления. То есть когда нужно а) откатить незавершенные операции и б) вывести «красивое» сообщение об ошибке. С RAII мы получаем код обработки ошибок почти бесплатно.

Если от ошибки можно восстановиться, то, в зависимости от языка, надо реализовывать это с помощью Either, кодов ошибок, или проверяемых исключений. Здесь обычные исключения не нужны, да.
тут скорее вопрос в том, а нужно ли восстанавливаться после ошибки. Допустим можно вполне легко восстановиться после ошибки записи данных в БД, потому можно было бы в этом месте вернуть false в значении «не записалось попробуйте ещё» и пойти дальше обрабатывать ситуацию на верхних уровнях вида «например выдать сообщение о том, что надо попробовать обновить страничку». Но в силу того, что это лежит за пределами ответственности этого кода (например неверный пароль БД в конфиге, ошибка типа данных или что ещё, что коду было обещано, но не предоставлено), коду следует бросить исключение и прекратить работу, потому что сломалось что-то, за что он не ответственен. Меня тут в комментариях когда-то очень правильно поправили, что всё дело в предусмотренном к методу контракте. Если он предусматривает такую ситуацию, значит всё хорошо, а если не предусматривает (если это недопустимое состояние окружения, например), значит должно генерироваться исключение даже в тех случаях, когда гипотетически можно было просто вернуть статус провала операции и сделать вид, что всё ок. В каждом конкретном случае надо смотреть что находится в зоне ответственности, а что нет, и исходя из этого бросать исключения или возвращать статусы ошибок. Например если код валидирует данные и видит в них ошибку — то нужно возвращать статус ошибки (они по задумке могут быть невалидными), но если при попытке записать отвалидированные данные в БД выяснилось, что они невалидные — исключение (они по задумке обязаны быть валидными).
Sign up to leave a comment.

Articles