Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Общие вопросы по .NET и C# > throw or not throw


Автор: nikitao 8.6.2011, 23:02
Добрый день.
Хочется разобраться в этой теме. Как я понял единства и стандарта в использовании исключений не наблюдается.
К примеру Рихтер и Макконнелл 2 ортогональных мнения высказывают в своих книгах ( как мне показалось).
Первый говорит - используйте их как можно больше и лучше. Все минусы с лихвой окупятся их плюсами.
Второй -  "Генерируйте исключения , только в исключительных ситуациях".

В Рихтере приводится пример ( пишу по памяти ) :
Есть клиент банка , у него есчь счет. Он хочет перевести деньги на другой счет. Пишем :
Код

Account from=new Account(бла бла бла);
Account to=new Account(бла бла);
from.Transfer(to,500);

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

Очень популярна фраза "Генерируйте исключения , только в исключительных ситуациях". И дальше автор приводит какой то травильный пример. Создается впечатление , что понятие исключительная ситуация у программиста впитывается с молоком матери smile 
Я серьезно , для меня так самое сложное понять для себя - что такое исключительная ситуация.
Просто напросто, если я подумал о какой то ситуации , что она может возникнуть - то она уже не исключительная , раз я ее смог предусмотреть.

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

Есть какой то класс , которые отправляет сообщения на сервер

Код

ServiceClient client=new ServiceClient();
client.SendPost(message);


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

Или вот еще пример , где не понятно :
Код

Stream.ReadByte();

Возвращает -1 , если достигнут конец потока. С чего ? Почему он возвращает -1 , а не генерирует исключение ?
По этой логике при делении на 0 - надо возвращать бесконечность или минус бесконечность.

Хочется выработать какой то внутренний стандарт относительно этого вопроса.

Спасибо.

Автор: wester 8.6.2011, 23:45
Я думаю что это все зависит от архитектуры проекта в целом. 
Может архитектура закладывает падать при любой ошибке - например медицинская техника, где при любой ошибке безопаснее выключать аппарат что бы не случилось опасности. 
для биллинга исключения на мой взгляд лучше не закладывать, а все писать в большой лог.
Про деление на ноль. Лично я возвращал бы -1.
В своих проектах исключения я просто заменяю на енумы. Удобнее и понятнее.

Цитата

Хочется выработать какой то внутренний стандарт относительно этого вопроса.

это все вырабатывается собственным опытом. 

везде писал "лично" ибо все зависит от архитектуры проекта

Автор: nikitao 8.6.2011, 23:55
Ну вот на самом деле хочется как раз узнать кому что их личный опыт подсказывает.

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

Автор: ДобренькийПапаша 9.6.2011, 07:30
nikitao, спасибо за тему. Тоже очень хотелось бы узнать мнение более опытных коллег. Особенно в случае, когда архитектура никакая не ясна. С чётким проектированием и разработкой архитектуры начинаются далеко не все проекты и не во всех компаниях smile

Автор: Stolzen 9.6.2011, 15:06
Я думаю, прежде всего следуюет определиться с методом - какой контракт между ним и вызывающим его? 

Например, возьмем класс http://download.oracle.com/javase/1.4.2/docs/api/java/util/Iterator.html в джаве. По контракту, перед вызовом метода next() нужно вызывать метод hasNext(), иначе в некоторый момент можно поймать исключение NoSuchElementException. Ситуация вполне обыденная - мы часто пользуемся итераторами для прохождения по коллекциям, однако мы всегда соблюдаем контракт (и исключение заставляет нас это делать).

Поэтому при переводе денег, я думаю, лучше сделать вызов метода hasEnoughMoney - и с чистой совестью бросать эксепшн, если денег на счету нет. Тут -1 или null не вернешь - исключительная ситуация прямо напрашивается.

В случае с сервером все исключения, я думаю, можно объеденить по какому-то признаку, и бросать, скажем, два - AuthorisationException, ConnectException. Остальные можно не афишировать, но бросать так же (внутренняя ошибка сервера - большая проблема). В джаве для таких случаев для первых двух лучше использовать checked exception, для остальных - unchecked. Однако при правильной архитектуре сервера потребность в большом количестве исключений отпадет. Например, сделать метод isReachable() и возвращать булеву. После этого делать login() и бросать в нем ConnectionException (его сделать unchecked, если для джавы). Затем передавать какие-либо данные. Ну идея понятна, я думаю. 

Насчет перечислений - я думаю иногда есть смысл, но лучше перечисление добавлять непосредственно в сам класс и делать его недоступным извне - для сохраниения текущего состояния и быстрой проверки допустимых в текущий момент операций - например, сервер переходит в стостояние CONNECTED после соединения и т.п. Вот http://0agr.ru/blog/2011/05/30/%D0%BA%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D0%BE%D1%80-%D0%BF%D0%BE-%D1%83%D0%BC%D0%BE%D0%BB%D1%87%D0%B0%D0%BD%D0%B8%D1%8E-%D0%B8-%D1%81%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8/ есть пример из книги EffectiveJava, в котором перечисление используется подобным образом. 

Некоторые программисты делают так - создают два метода. Один бросает исключение, когда контракт не соблюден, другие же просто возвращают null. Например, poll() и remove() в интерфейсе http://download.oracle.com/javase/1,5.0/docs/api/java/util/Queue.html в джаве задают именно такой контракт. 

В целом, я думаю, нужно делать так, как удобнее. Проверять на -1 при чтении потока иногда удобнее, хотя я бы сделал метод isEof() и бросал бы исключение (как в паскале).

Насчет бесконечности при делении на ноль - так возвращается при некоторых условиях (F тут обозначает float):
Код

System.out.println(10F / 0);

Код

Infinity


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

Добавлено через 2 минуты и 45 секунд
Цитата(nikitao @  9.6.2011,  00:55 Найти цитируемый пост)
А если в ф-цию передаются неверные параметры ( там null к примеру в кач-ве одного из параметров) вы тоже код возвращаете ?

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

Автор: wester 9.6.2011, 15:21
В последнем проекте использовал исключение только в одном случае исключение - если недоступен интернет-ресурс. В этом случае завершалась работа программы, ибо работать в таком случае нельзя. В остальных случаях я возвращал код ошибки через енумы.
По поводу передачи некорректного параметра - проверяю параметры на разных уровнях. К примеру у меня реализована работа с БД и заполнением ее данными из инета. Данные которые поступают от пользователя, проверяю на первом уровне и если все данные верны, то вызываю методы (второго уровня) заполнения БД. В противном случае возвращаю код ошибки. Этакий Code Contracts , только со своими плюсами.

маленькое обсуждение http://stackoverflow.com/questions/161942/how-slow-are-net-exceptions

Автор: mihryak 9.6.2011, 17:34
Цитата(wester @  9.6.2011,  00:45 Найти цитируемый пост)
для биллинга исключения на мой взгляд лучше не закладывать, а все писать в большой лог.

т.е. попробовать списать с клиента деньги за услугу, узнать, что их не хвататет - ну и фиг с ним, потом в логе кто-нибудь заметит, так?

Цитата(Stolzen @  9.6.2011,  16:06 Найти цитируемый пост)
Поэтому при переводе денег, я думаю, лучше сделать вызов метода hasEnoughMoney - и с чистой совестью бросать эксепшн, если денег на счету нет. Тут -1 или null не вернешь - исключительная ситуация прямо напрашивается.

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

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

например,
метод List<T>.Remove(T) может вполне себе спокойно вернуть false, если объект не найден
но Dictionary<TKey, TValue)[Tkey] не может придумать, что бы ему такого вернуть, если ключ не найден
ServiceClient.SendPost() может завалиться по совсем разным причинам, тут исключение, т.к. использовать коды ошибок в .нете не принято

Автор: Fieral 9.6.2011, 22:08
Для установки эксепшонов работает простое правило: Выбрасывай исключение только когда больше ничего сделать нельзя.

Это автоматически отвечает на вопрос: "Что такое исключительная ситуация?" - это когда больше ничего сделать нельзя.
Если можно - разруливай.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)