но сбой программного обеспечения вашего клиента все еще не очень хорошая вещь
Это, безусловно, хорошая вещь.
Вы хотите, чтобы все, что оставляет систему в неопределенном состоянии, могло остановить ее, потому что неопределенная система может делать такие неприятные вещи, как повреждение данных, форматирование жесткого диска и отправлять президенту электронные письма с угрозами. Если вы не можете восстановить систему и вернуть ее в определенное состояние, то ответственным за это является сбой. Именно поэтому мы строим системы, которые рушатся, а не тихо разрывают себя на части. Теперь мы уверены, что нам всем нужна стабильная система, которая никогда не выходит из строя, но мы действительно хотим этого, когда система остается в определенном предсказуемом безопасном состоянии.
Я слышал, что исключения как поток управления считаются серьезным антипаттерном.
Это абсолютно верно, но это часто неправильно понимают. Когда они изобрели систему исключений, они боялись, что нарушат структурированное программирование. Структурное программирование поэтому у нас есть for
, while
, until
, break
, и , continue
когда все , что нужно, чтобы сделать все , что есть goto
.
Дейкстра научил нас тому, что неформальное использование goto (то есть прыжки вокруг, где угодно) превращает чтение кода в кошмар. Когда они дали нам систему исключений, они боялись, что они изобретают goto. Поэтому они сказали нам не «использовать это для управления потоком», надеясь, что мы поймем. К сожалению, многие из нас этого не сделали.
Как ни странно, мы не часто злоупотребляем исключениями для создания спагетти-кода, как мы привыкли к goto. Сам совет, кажется, вызвал больше проблем.
Принципиально исключения касаются отклонения предположения. Когда вы просите сохранить файл, вы предполагаете, что файл может быть и будет сохранен. Исключение, которое вы получаете, когда оно не может быть связано с тем, что имя незаконно, HD заполнен, или потому что крыса прогрызла ваш кабель для передачи данных. Вы можете обрабатывать все эти ошибки по-разному, вы можете обрабатывать их одинаково, или вы можете позволить им остановить систему. В вашем коде есть удачный путь, где ваши предположения должны выполняться. Так или иначе, исключения сбивают вас с этого счастливого пути. Строго говоря, да, это своего рода «управление потоком», но это не то, о чем вас предупреждали. Они говорили о чепухе, как это :
«Исключения должны быть исключительными». Эта маленькая тавтология родилась потому, что разработчикам систем исключений нужно время для построения трассировок стека. По сравнению с прыжками это медленно. Кушает процессорное время. Но если вы собираетесь войти в систему и остановить систему или, по крайней мере, остановить текущую интенсивную обработку, прежде чем начинать следующую, у вас есть время для уничтожения. Если люди начинают использовать исключения «для управления потоком», все эти предположения о времени все уходят в окно. Таким образом, «Исключения должны быть исключительными» действительно были даны нам в качестве оценки производительности.
Гораздо важнее, чем то, что нас не смущает. Сколько времени вам понадобилось, чтобы обнаружить бесконечный цикл в приведенном выше коде?
НЕ возвращайте коды ошибок.
... это хороший совет, когда вы находитесь в кодовой базе, которая обычно не использует коды ошибок. Почему? Потому что никто не забудет сохранить возвращаемое значение и проверить ваши коды ошибок. Это все еще хорошее соглашение, когда вы находитесь в C.
OneOf
Вы используете еще одно соглашение. Это хорошо, если вы устанавливаете соглашение, а не просто сражаетесь с другим. Забавно иметь два соглашения об ошибках в одной кодовой базе. Если каким-то образом вы избавились от всего кода, который использует другое соглашение, тогда продолжайте.
Мне нравится конвенция сама. Одно из лучших объяснений этого я нашел здесь * :
Но, насколько мне это нравится, я все равно не собираюсь смешивать это с другими соглашениями. Выберите один и придерживайтесь его. 1
1: Под этим я подразумеваю, не заставляйте меня думать о более чем одном соглашении одновременно.
Последующие мысли:
Из этой дискуссии я забираю следующее:
- Если вы ожидаете, что непосредственный вызывающий объект будет перехватывать и обрабатывать исключение большую часть времени и продолжать его работу, возможно, по другому пути, он, вероятно, должен быть частью возвращаемого типа. Необязательно или OneOf может быть полезным здесь.
- Если вы ожидаете, что непосредственный вызывающий абонент не будет перехватывать исключение большую часть времени, сгенерируйте исключение, чтобы избежать глупости ручной передачи его в стек.
- Если вы не уверены, что будет делать непосредственный абонент, возможно, предоставьте и то, и другое, например Parse и TryParse.
Это действительно не так просто. Одна из фундаментальных вещей, которую вы должны понять, это то, что такое ноль.
Сколько дней осталось в мае? 0 (потому что это не май. Это уже июнь).
Исключения - это способ отклонить предположение, но они не являются единственным. Если вы используете исключения, чтобы отклонить предположение, вы покидаете счастливый путь. Но если вы выбрали значения для отправки по счастливому пути, сигнализирующему о том, что все не так просто, как предполагалось, вы можете оставаться на этом пути, пока он может справиться с этими значениями. Иногда 0 уже используется для обозначения чего-либо, поэтому вы должны найти другое значение, чтобы сопоставить свое предположение с отклонением идеи. Вы можете узнать эту идею по ее использованию в старой доброй алгебре . Монады могут помочь с этим, но это не всегда должна быть монада.
Например 2 :
IList<int> ParseAllTheInts(String s) { ... }
Можете ли вы придумать какую-либо вескую причину, по которой это должно быть разработано так, чтобы оно преднамеренно выбрасывало что-либо? Угадайте, что вы получаете, когда нет int может быть проанализирован? Мне даже не нужно тебе говорить.
Это признак хорошего имени. Извините, но TryParse - не моя идея хорошего имени.
Мы часто избегаем исключения, когда ничего не получаем, когда ответом может быть несколько вещей одновременно, но по какой-то причине, если ответом является либо одно, либо ничто, мы зацикливаемся на том, что он дает нам одну вещь или бросок:
IList<Point> Intersection(Line a, Line b) { ... }
Действительно ли параллельные линии должны вызывать здесь исключение? Неужели так плохо, если этот список никогда не будет содержать более одного пункта?
Может быть, семантически вы просто не можете принять это. Если так, то жаль. Но, может быть, монады, которые не имеют произвольного размера, как у List
вас, заставят вас чувствовать себя лучше.
Maybe<Point> Intersection(Line a, Line b) { ... }
Монады - это небольшие коллекции специального назначения, предназначенные для использования определенным образом, чтобы избежать необходимости их тестирования. Мы должны найти способы справиться с ними независимо от того, что они содержат. Таким образом, счастливый путь остается простым. Если вы взломаете и протестируете каждую монаду, которую вы касаетесь, вы используете их неправильно.
Я знаю, это странно. Но это новый инструмент (ну, для нас). Так что дайте ему немного времени. Молотки имеют больше смысла, когда вы перестаете использовать их на винтах.
Если вы будете баловать меня, я хотел бы обратиться к этому комментарию:
Почему ни один из ответов не объясняет, что монада Either не является кодом ошибки, и при этом OneOf не является? Они принципиально разные, и, следовательно, вопрос, похоже, основан на недоразумении. (Хотя в измененной форме это все еще актуальный вопрос.) - Конрад Рудольф 4 июня 18 в 14:08
Это абсолютно верно. Монады гораздо ближе к коллекциям, чем исключения, флаги или коды ошибок. Они делают прекрасные контейнеры для таких вещей при разумном использовании.
Result
;-)