У меня есть выбор: либо этот весь список добавить в throw, либо перехватить исключения локально, добавить в лог-файл, а функция вернет null вместо ответа. Что лучше?
И вообще, как вы решаете проблему с накапливанием исключений? Стараетесь их как можно раньше обработать и вернуть null (или прочий код возврата)? Или заменяете на другое исключение, не проверяемое? Или все пихаете в throws и хрен с ним?
Здравствуйте, 0K, Вы писали:
0K>В общем, как правильно?
Лично я их в основном конверчу в наследники от RuntimeException в обработчике. В довольно редких случаях возвращаю значение по умолчанию, или какую нидь константу в случае ошибки, если это допустимо (а это допустимо, например, при форматировании даты — лучше пусть юзер увидит ошибку на месте даты, чем ошибка будет прокинута на самый верх). Checked конечно использую, но редко. Например в случае вебсервиса я явно говорю, какого типа исключения кидаются.
Здравствуйте, 0K, Вы писали:
0K>Такой вопрос. Нормально ли, если у метода будет штук 10 throws'ов? Причем не у единичного, а у многих методов.
0K>Вроде в открытых библиотеках такого не встречал. А вот когда я пишу -- получается по 10-15 этих throws'ов на метод.
Может быть в данном случаи проблема не в накапливании throws'ов? Если судить по описанию метода, то он выполняет несколько различных функции, отсюда и большое количество потенциальных ошибок, требующих обработки. Может быть стоит разделить данный метод на несколько более мелких, и при этом в каждом из них будет небольшое количество исключений,
а вызывающий их метод мог бы их обрабатывать и генерировать исключение, которое было бы более информативно в контексте его использования.
Здравствуйте, 0K, Вы писали:
0K>В общем, как правильно?
Правильно не раскрывать детали имплементации в публичном контракте. Перечисленные исключения это детали реализации. Их нужно собрать, обработать и вернуть что-то более приемлимое с точки зрения контракта. Например так:
...
} catch (TimeoutException e) {
throw new ExternalServerAPIException("External API call was failed because of timeout", e);
} catch (OtherExceptionFromTheList e) {
throw new ExternalServerAPIException("External API call was failed", e);
}
Не обязательно это будет одно исключение, может быть целая иерархия.
Почему нельзя возвращать null. null не отвечает контракту функции и требуется либо вводить костыли типа аннотации @Nullable с последующей автоматической проверкой, либо ловить злые баги, когда в одном месте забыли проверку на null.
Почему нельзя бросать RuntimeException. RuntimeException не описывается в контракте. Соответственно автор клиентского кода может вообще не догадаться что метод что-то там кидает.
Короче, checked exceptions придумали не просто так. К сожалению мало кто умеет ими пользоваться.
Re[2]: Накапливание throws'ов -- нормально ли это?
Здравствуйте, Andrey_spb, Вы писали:
A_>Может быть в данном случаи проблема не в накапливании throws'ов? Если судить по описанию метода, то он выполняет несколько различных функции, отсюда и большое количество потенциальных ошибок, требующих обработки. Может быть стоит разделить данный метод на несколько более мелких, и при этом в каждом из них будет небольшое количество исключений, A_>а вызывающий их метод мог бы их обрабатывать и генерировать исключение, которое было бы более информативно в контексте его использования.
Внутри этот метод вызывает более мелкие. Т.е. он не перегружен логикой. И сам выполняет одну конкретную функцию, по этому убирать его не стоит...
=сначала спроси у GPT=
Re[2]: Накапливание throws'ов -- нормально ли это?
Здравствуйте, Miroff, Вы писали:
M>Не обязательно это будет одно исключение, может быть целая иерархия.
Да интересный вариант. Нужно взять на заметку.
M>Почему нельзя возвращать null. null не отвечает контракту функции и требуется либо вводить костыли типа аннотации @Nullable с последующей автоматической проверкой, либо ловить злые баги, когда в одном месте забыли проверку на null.
Абсолютно согласен. Возврат null -- это почти то же самое, что возврат кода ошибки (устаревшая парадигма языка C).
M>Почему нельзя бросать RuntimeException. RuntimeException не описывается в контракте. Соответственно автор клиентского кода может вообще не догадаться что метод что-то там кидает.
Интересно.
M>Короче, checked exceptions придумали не просто так. К сожалению мало кто умеет ими пользоваться.
Я уже начал относится к ним как к злу. Особенно угнетает, что при переопределении метода нельзя добавить новых исключений. Почему нельзя добавить? Ведь я добавляю логику -- значит могут и новые исключения появится.
Здравствуйте, 0K, Вы писали:
0K>Такой вопрос. Нормально ли, если у метода будет штук 10 throws'ов? Причем не у единичного, а у многих методов.
0K>Вроде в открытых библиотеках такого не встречал. А вот когда я пишу -- получается по 10-15 этих throws'ов на метод.
Нет не очень нормально. Exception — тип не явно возвращаемый методом, а метод — часть интерфейса, стало быть Exception тоже относится к интерфейсу, а интерфейс мы проектируем так что бы им было удобно пользоваться. Методом который выбрасываем много Exception по моему не очень удобно пользоваться .
Я делаю так(ну это отправная точка для проектирования): предположим есть метод работа которого может завершиться аварийно и у меня есть три варианта действий на этот случай для преодоления трёх разных причин, значит будет три Exception.
Программа — мысли спрессованные в код.
Re[3]: Накапливание throws'ов -- нормально ли это?
Здравствуйте, 0K, Вы писали:
0K>Я уже начал относится к ним как к злу. Особенно угнетает, что при переопределении метода нельзя добавить новых исключений. Почему нельзя добавить? Ведь я добавляю логику -- значит могут и новые исключения появится.
Вообще, checked это не зло. Это очень и очень хорошая фича, которую просто нужно правильно применять. Например когда пишешь библиотечные классы или какие то API, то наружу обязательно отдавать именно checked. Так как тем самым описывается контракт, и все пользователи этого API будут знать об исключениях, и никогда не забудут про обработку. checked — это просто указание клиенту о том, что такие ситуации следует обрабатывать, от них никто не застрахован. А вот в случаях, когда все крутится внутри определенного модуля или внутри приложения, от checked будет больше вреда, чем пользы. Собственно я делаю так — checked от внешних либ прокидываю как наследник от RuntimeException (он используется внутри модуля или приложения), а если у нас это определенный довольно сложный модуль со своим API, которым пользуются другие, то перед вызовом ловим все Exception и прокидываем еще раз строго задокументирыванный checked exception.
Re[3]: Накапливание throws'ов -- нормально ли это?
От:
Аноним
Дата:
19.12.10 10:31
Оценка:
0K>Я уже начал относится к ним как к злу. Особенно угнетает, что при переопределении метода нельзя добавить новых исключений. Почему нельзя добавить? Ведь я добавляю логику -- значит могут и новые исключения появится.
Судя по всему, вы плохо понимаете, что такое полиморфизм. Новые исключения — нарушение контракта, которое может привести к непредсказуемым последствиям у пользователей вашей реализации. Они-то имеют дело с интерфейсом и ожидают вполне определенного поведения. Почему нарушение контракта это плохо? Представьте себе, что вы обновили версию сторонней библиотеки и, при неизменном интерфейсе, она начала выбрасывать в определенных ситуациях (очень неожиданно для вас) новое исключение, которое вы не обрабатываете, и которое может попасть к конечному пользователю в обход всех ваших тестов. Ухудшение качества продукта, заметное пользователю, это, в конечном счете, ваши финансовые потери.
В Java с незапамятных времен (J2SE 1.4) есть механизм под названием Exception chaining, которым можно и нужно пользоваться. У ошибки при движении от более простого и низкоуровневого метода (например, чтение из файла) к более сложному (например, обработка запроса клиента) появляются новые семантические значения (нет доступа к файлу -> невозможно загрузить объект -> сервис недоступен), которые нужно отражать в модели исключений (например, SecurityException -> ResourceException -> ServiceException). Т.о. самый правильный подход — на каждом уровне иметь одно или иерархию исключений, соответствующих смыслу происходящего на этом уровне.
На уровне файловой системы это IOException и потомки (FileNotFound etc), на уровне слоя доступа к данным это, скажем, ResourceException (ResourceAccess и т.п.), на уровне слоя бизнес-сервисов — ServiceException и потомки (ServiceAccess, InternalError, InvalidRequest etc). Клиента совершенно не волнуют подробности о том, в каком файле ваша реализация хранит данные, его интересует более высокоуровневая диагностика — была ли это ошибка доступа к службе, неправильный запрос или внутренний сбой.
Re[2]: Накапливание throws'ов -- нормально ли это?
Здравствуйте, Baudolino, Вы писали:
B>В Java с незапамятных времен (J2SE 1.4) есть механизм под названием Exception chaining, которым можно и нужно пользоваться. У ошибки при движении от более простого и низкоуровневого метода (например, чтение из файла) к более сложному (например, обработка запроса клиента) появляются новые семантические значения (нет доступа к файлу -> невозможно загрузить объект -> сервис недоступен), которые нужно отражать в модели исключений (например, SecurityException -> ResourceException -> ServiceException). Т.о. самый правильный подход — на каждом уровне иметь одно или иерархию исключений, соответствующих смыслу происходящего на этом уровне. B>На уровне файловой системы это IOException и потомки (FileNotFound etc), на уровне слоя доступа к данным это, скажем, ResourceException (ResourceAccess и т.п.), на уровне слоя бизнес-сервисов — ServiceException и потомки (ServiceAccess, InternalError, InvalidRequest etc). Клиента совершенно не волнуют подробности о том, в каком файле ваша реализация хранит данные, его интересует более высокоуровневая диагностика — была ли это ошибка доступа к службе, неправильный запрос или внутренний сбой.
То, как это выглядит в коде — ужасно и порождает кучу boilerplate кода.
Возможно эту идею оживил бы синтаксический сахар вроде
int getActiveUserCount() throws DALException if SQLException {
}
но так, как оно выглядит сейчас (тысячи try-catch-throw), оно очень тяжело применимо.
Здравствуйте, 0K, Вы писали:
0K>Такой вопрос. Нормально ли, если у метода будет штук 10 throws'ов? Причем не у единичного, а у многих методов. 0K>Вроде в открытых библиотеках такого не встречал. А вот когда я пишу -- получается по 10-15 этих throws'ов на метод.
Как уже сказали, скорее всего надо разделить методы на более мелкие, отвечающие за одно конкретное действие. Плюс надо разделить исключения на runtime и non-runtime.
Метод, который вызывает много мелких методов вполне может содержать несколько объявленных исключений. Ничего страшного в этом не вижу.
0K>У меня есть выбор: либо этот весь список добавить в throw, либо перехватить исключения локально, добавить в лог-файл, а функция вернет null вместо ответа. Что лучше?
Возвращать null — самое плохое, что можно придумать. Во-первых, не надо смешивать два разных подхода к сообщению об ошибке: коды возврата и исключения. В яве есть исключения, так что возвращаемое значение должно содержать только легальные данные, говорящие о корректном результате выполнения метода. Во-вторых, это первый шаг к получению NPE. И вполне вероятно, твой код начнет содержать дополнительное объявление throws NullPointerException, то есть, усугубишь то, от чего пытаешься избавиться.
0K>И вообще, как вы решаете проблему с накапливанием исключений? Стараетесь их как можно раньше обработать и вернуть null (или прочий код возврата)? Или заменяете на другое исключение, не проверяемое? Или все пихаете в throws и хрен с ним?
Библиотечные классы только прокидывают исключения далее наверх с дополнительными комментариями, описывающими, при каких условиях они были брошены, если это надо.
Обработка исключений только на самом верху. А там обычно пишется в лог, на консоль и в зависимости от исключения может выдаваться приличное уведомление пользователю.
Re[3]: Накапливание throws'ов -- нормально ли это?
vsb>То, как это выглядит в коде — ужасно и порождает кучу boilerplate кода.
Я бы не стал называть это boilerplate кодом. То, как вы обрабатываете ошибки, определяет качество вашей реализации.
vsb>Возможно эту идею оживил бы синтаксический сахар вроде vsb>
vsb>int getActiveUserCount() throws DALException if SQLException {
vsb>}
vsb>
А вот это как раз ужасно, по крайней мере, в вашем примере. SQLException бывают разные и заворачивать их в обертку одного типа неправильно.
vsb>но так, как оно выглядит сейчас (тысячи try-catch-throw), оно очень тяжело применимо.
Не вижу ничего тяжелого.
Re[2]: Накапливание throws'ов -- нормально ли это?
Здравствуйте, Donz, Вы писали:
D>И вполне вероятно, твой код начнет содержать дополнительное объявление throws NullPointerException...
NPE не надо объявлять
D>Обработка исключений только на самом верху.
Наверное, всё же не всегда? Код может быть устойчивым к некоторым типам исключений.
Re[3]: Накапливание throws'ов -- нормально ли это?
Здравствуйте, Baudolino, Вы писали:
D>>И вполне вероятно, твой код начнет содержать дополнительное объявление throws NullPointerException... B>NPE не надо объявлять
Я не про требования языка, а про правильных подход
Если код явно подразумевает возможность NPE (например, явное throw new NPE()), то желательно.
D>>Обработка исключений только на самом верху. B>Наверное, всё же не всегда? Код может быть устойчивым к некоторым типам исключений.
Да, речь идет о исключениях, которые уже должны попасть в лог.