- PVSM.RU - https://www.pvsm.ru -

Я каждый день пишу код на сишарпе, и натыкаюсь на одну проблему: я трачу кучу времени на то, чтобы решить, как быть, если что-то идёт не по плану.
У меня есть приличный опыт работы с другими языками программирования, и стандартный подход по работе с ошибками в C# мне не нравится. Но языки и платформы устроены так, что ты решаешь проблемы не как считаешь нужным, а так, как принято.
Эти размышления меня измучили, и я систематизировал свои знания и идеи по обработке исключительных случаев.
Возьму простой пример. Допустим у нас есть сервис, который отдаёт нам модель юзера по Id.
Вот такой:

Если мы передадим айдишник существующего юзера, метод отработает корректно, и мы получим свои данные. Но. Такого пользователя может не быть в системе, и вот тут нам нужно сесть, и хорошенько подумать, как должен вести себя этот метод.
Давайте посмотрим, какие у нас есть варианты.
Тут все просто. Метод ищет пользователя, если, не находит — выплевывает исключение.
Примерно так:

Пользоваться таким методом можно вот так:

Плюсы подхода очевидны.
Но минусов — тьма. И просто закрыть глаза на них — не получится.
Лучше всего понять, чем плохи исключения, можно, когда используешь чужой, плохо задокументированный код. Есть условный метод GetById, а что он станет делать, если не найдет — ну ты понятия не имеешь. Вернет null? Выбросит какое-то исключение? Я писал код, в котором вызывал чужой метод, оборачивал в try-catch, и чекал результат на нулл. Создателя того метода я бы с радостью прибил. Ну, по крайней мере в тот момент.
Идея завязана на out параметры в сишарпе. Выглядит вот так:

Если все норм, мы возвращаем true, и присваиваем out переменной user найденное значение. Если не норм, отдаём false, а out переменную заполняем дефолтным значением (в случае с классом это будет null).
Использовать такой метод следует так:

У подхода много плюсов:
Минусы тоже есть:
Тоже распространенный для дотнета подход, когда мы отдаем найденное значение, а иначе null.
Делается так:

А использовать вот так:

Плюсы:
Минусы:
Идея простая. Есть отдельная сборка, в ней лежит абстрактный класс Maybe, у него два наследника, Success и Failure. Отдельная сборка и интёрнал конструктор нужны, чтобы гарантировать — наследников всегда будет только два.
Тогда метод будет выглядеть вот так:

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

Здесь не так много плюсов, но они очень увесистые.
А минусы — паршивые.
Короче, монады хороши с логической точки зрения, но их реализация в текущем сишарпе выглядит ну очень некрасиво и непривычно. Это само по себе — большая проблема. Но она скоро решится — разработчики языка обещают поставить в одной из будущих версий Discriminated Unions — именно с помощью таких конструкций монады реализованы во всяких F#-ах или Haskell-ях. Код с монадами перестанет быть уродливым и запутанным, но остается проблема с тем, что подход для шарпистов непривычен. Кроме того весь уже написанный код набит исключениями, и переезжать никто не будет, а куча разных способов решать одинаковые проблемы в одном проект — это совсем не хорошая идея.
Да, у меня в статье Maybe представлена исключительно как концепт. У неё есть отличные реализации в виде библиотек. В случае, если нужно передать информацию об ошибке, используется монада Either/Result. Для которой так же существуют сторонние решения.
Если мне не нужно знать, что там за ошибка, сам случай не сверх критичный, у меня C#8+ со включенным nullable, у всех пользователей кода тоже — я бы использовал SomeOrDefault. Если Nullable нету, тогда tryPattern. Если момент критичный, тогда Maybe.
Если у меня нет nullable, и кейс асинхронный — значит try pattern и someOrDefault мне не пойдет, и тогда я бы тоже взял Maybe.
Соответственно, если хотим передать данные об ошибке, тогда лучше использовать Result монаду.
Exception хорошо подходит для случаев:
Самый увесистый минус Ecxeption лежит даже не в механике их работы, он в культуре использования. Процессы разработки часто устроены так, что человек понятия не имеет, что делать если операция провалилась. И он делает что, он создает исключение, выбрасывает его, и полностью снимает ответственность как с себя, так и с модуля, который он делает. Этот подход страусов, а не инженеров. Видишь проблему, запихиваешь башку в песок, и делаешь вид, что все идёт окей.
Возможно на существуещем механизме исключений в сишарпе можно было бы работать по-другому, но я думаю, с учетом всего того кода, который уже написан, они достаточно дискредетировали себя, чтобы посмотреть в сторону других практик.
Это поднимает куда более серьезную проблему. Ладно, подход с исключениями технически несовершенен, но даже если и был бы, есть штука, куда более несовершенная. Программисты. Человеческий фактор. Я вот пришел сюда, такой умный, начал учить как обрабатывать ошибки, а потом заглянул в код своих проектов, и везде вижу одно и то же- мой класс, как разработчика, недостаточно высок, я постоянно не понимаю, как разруливать исключительные ситуации. Я их игнорирую, логгирую, и прячу. Кроме тех мест, где они кому-то уже навредили, и меня заставили именно там все продумать. И никакие технические возможности языка не заставят меня продумывать все.
Но. Они заставят продумывать чуть больше, может быть, на 5%, может на 1, может на 10. И это вообще единственный способ хоть как то уменьшать влияние человеческого фактора. Поэтому, я не вижу причин, чтобы отказываться от тех же монад или гарантированно обрабатываемых исключений.
Я привел четыре концептуальных подхода к работе с ошибками, но на деле их намного больше. Например приходит в голову подход в Go — отдавать из функций кортеж (результат*ошибка). Как по мне- очень спорный способ, но я открыт к дискуссии. Делитесь мыслями в комментариях, какие ещё у нас есть варианты, и в чем их преимущество.
Код примеров лежит здесь [1].
Подыскиваете виртуальный сервер [2] для отладки проектов, сервер для разработки и размещения? Вы точно наш клиент :) Посуточная тарификация серверов самых различных конфигураций, антиDDoS.
Автор: Philipp Ranzhin
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/358128
Ссылки в тексте:
[1] здесь: https://github.com/philippranzhin/my-obrecheny-articles/tree/main/samples/ExceptionHandling
[2] виртуальный сервер: https://vdsina.ru/cloud-servers?partner=habr139
[3] Источник: https://habr.com/ru/post/523618/?utm_source=habrahabr&utm_medium=rss&utm_campaign=523618
Нажмите здесь для печати.