Как стать автором
Обновить

Комментарии 24

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

Цель статьи показать проблемы концепции: не наглядность, не предсказуемость и перенос ошибок из compile time в runtime, поэтому не могу согласиться с вашим тезисом "Нет никаких проблем с Ексепшном..."


В заметке я не ставил себе задачу ответить на вопроса "почему концепция exception появилась?", но для себя я отвечаю на этот вопрос так:


1) Появилась концепция давно, тогда когда языковые конструкции, в популярных языках, не позволяли удобно обойтись без нее.
2) Концепция Exception упрощает ознакомления с языком, ее просто можно не змечать довольно долго и при этом разрабатывать код.


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

То что вы предлагаете - это кошмар и ужас! Зачем вам знать какого типа может быть exception? Вам нужно знать можно-ли на этом этапе восстановиться от какого-то определённого типа и все. Все неизвестные/непредусмотренные exceoуходят выше.

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

И ещё есть подозрение, что у вас похоже Java background. Иначе абсурдные требование описывать exception не появилось-бы.

Вам нужно знать можно-ли на этом этапе восстановиться от какого-то определённого типа и все.

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


Что касается возврата кодов, я как раз считаю что из за них появилась концепция exception и надо их избегать.


Я предлагаю использовать концепцию discriminant union или то как она сейчас реализуется в ООП языках к примеру через OneOf — каждый метод должен явно описать все возможные варианты ихода вызова. И тогда при каждом использование метода у разработчика будет полная картина того что может произойти и он сам явно опишет как он реагирует на каждый исход, может и игнорировать если хочет.


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

Не. Так не пойдёт. Типов exception могут быть сотни на этом уровне. Заставлять обрабатывать все - тратить время программиста впустую. А не заставлять - возвращаться к кодам, где основная проблема эквивалентно проглоченым исключением. Мало кто проверять будет. И будут абсолютно правы.

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

Это как раз яркий пример пагубного влияние концепции Exception на тех. долг проекта. Разработчики годами, без злого умысла, но по причинам описанным в заметке, не могли обрабатывать все варианты исходов вызовов методов, что привело к тому что на верхнем уровне их теперь сотня и все что с ними делают, судя по вашим словам, просто игнорируют(я не знаю что внятного можно сделать с общим типом Exception кроме как вызвать toString и записать в лог).

Минуточку! Как раз все наоборот! Я привел пример хорошей системы, которая не требует обработки всех типов исключейний по-разному — подавляющее большенство типов исключений обрабатывается одинаково. Программисту просто не надо знать что это было NullReferenceException или SecurityException во многих случаях. Там, где это важно мы и так знаем как надо реагировать на определенный тип исключений. а на остальные нам тут плевать именно потому, что мы о них ничего не знаем.
Идея полного контроля порочна сама по себе потому, что требует действий, которые можно не совершать или действий, последствия которых невозможно представить да данном уровне.
Именно по-этому концепция неловленых исключений гораздо лучше концепции кодов (вы уж извините, но вы предлагаете именно концепцию кодов возврата). Лучше тем, что для того, чтобы все испортить надо делать телодвижения — отлавливать и игнорировать исключения в отличии от «пассивного игнорирования» кодов.

Главное отличие того что я предлагаю от концепции "кодов ошибок" заключается в том что коды можно проигнорировать в месте вызова метода и/или не обрабатывать их все. И что еще хуже, если даже разработчик постарался и описал реакцию на все коды ошибок, это не гарантирует что после обновления библиотеки в ней не добавлися новый код ошибки. Это в точности та же уязвимость что есть и у Exception-ов — нельзя гарантировать что ты обработал все варианты, даже если ты постарался.


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


Вот вы бы решились писать систему для самолета/автомибиля и т.д. зная что вы используете в коде метод который может выкинуть произвольный exception?

Ну хорошо допустим происходит изменение сигнатуры метода, но все разбивается о «var».
var result = SomeCall();

Тут хоть что добавляй оно тихо проигнорируется. Или вы предлагаете попутно от var отказаться?

Давайте не будем приводить екстремальные примеры типа космоса, ядерных реакторов и им подобных. А вот любую обычную систему (особенно web!) решусь и на самом деле пишу как и подавляющее большенство пишущих на языках с exceptions. Я просто не сичитаю проблемой, то что вы считаете основным недостатком. Вот вам контрпример: самый обычный Web API обрабатывает запросы внутренних сервисов. Внутри происходит одно из двух — NRE из-за багов или отваливается база потому, что… отваливается. Посмотрим на внешний сервис (если не нравится граница сервисов, то просто представьте два класса внутри одного приложения) — как ему поможет знание того, что именно произошло? В любом случае он может или перейти на запасной сервис, повторить запрос или просто вернуть ошибку. Знание того, что произошло никак не влияет на решение. Так зачем ему обрабатывать по разному? Не знать, а именно обрабатывать! В вашей концепции сервис будет обязательно иметь две ветки обработки, которые делают одно и то-же. При появлении третьего типа ошибок добавится еще ветка. Зачем?

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

Нет я не предлагаю отказаться от var. В примере выше можно заменить


OneOf<User, InvalidName, NameTaken> createUserResult = CreateUser(username);

на


var createUserResult = CreateUser(username);

и поведение не измениться и все еще придется написать обработчики для всех трех вариантов.


createUserResult.Match(
    user => ...,
    invalidName => ...,
    nameTaken => ...;
);

Целью заметки было показать опасные моменты концепции exception, и судя по тому что вы согласны что на "ядерной станции" вы бы нехотели использоваться exeption-ы, у нас тут недопонимания нет.


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

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

Давайте попробуем с вами обсудить один недостаток концепции исплючений в C# — нет никакой возможности узнать какие именно exception могут быть выброшенны при вызове метода. Вот соверешнно нет никакого способа узнать все варианты исходов вызова метода.


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


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

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

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

Давайте начнем с того как мне узнать хоть где-то(в системном коде или бизнес логике) какие эксепшены могут возникнуть при вызове функции? Я потом уже решу надо мне их обрабатывать или нет )

Никак. За абстракциями стоят реализации с разными исключениями.

Очень точная фраза.


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

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

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

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

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

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

Уточните пожалуйста что вы имеете ввиду под DI, dependency injection или dependency inversion?

dependency injection

Зарегистрируйтесь на Хабре, чтобы оставить комментарий