Общая проблема
При написании метода обслуживания есть преимущества и недостатки в том, чтобы бросать множество исключений по сравнению с их абстрагированием в общий ServiceException
. Если вы выбрасываете много исключений, вы сообщаете клиенту, что именно пошло не так, чтобы клиент мог более разумно справиться с ошибкой. Если вы абстрагируете их в один ServiceException
(например, через упаковка исключений), то вы скрываете детали того, что пошло не так, что может быть полезно в некоторых сценариях, но вредно в других.
Например, может быть полезно скрыть подробности сбоя в классе DAO, выбрасывая общий DaoException
. Таким образом, клиентам не нужно знать, читаете ли вы из файла, веб-службы или реляционной базы данных. Но предположим, что вы проверяете вводимые пользователем данные, и есть по крайней мере четыре вещи, которые могут пойти не так с вводом пользователя. Разве в последнем случае не разумнее выбросить четыре разных исключения, по одному на каждую ошибку, которая пошла не так с проверкой?
Моя конкретная проблема
В моем текущем проекте я работаю над методом обслуживания, который сохраняет древовидную структуру в реляционной базе данных. Он должен проверить дерево, и есть по крайней мере четыре вещи, которые могут быть неправильными с деревом. Может быть дублирующийся узел или в узле могут отсутствовать обязательные поля X, Y, Z. Таким образом, для метода обслуживания имеет смысл генерировать четыре разных исключения: DuplicateNodeException
, MissingXException
, MissingYException
, MissingZException
.
Но служебный метод также использует класс DAO, который генерирует DataAccessException
через JdbcTemplate. Хотелось бы узнать, как обращаться с этим DataAccessException
. Имеет ли смысл обернуть его как ManagerException
, а затем снова как ServiceException
и обрабатывать как ServiceException
? Тогда я бы смешал оба подхода: (а) выбрасывать конкретные описательные исключения и (б) генерировать общие исключения оболочки. Хорошая идея - смешать здесь эти подходы?
Причина, по которой я спрашиваю, заключается в том, что мне кажется немного странным отловить эти четыре исключения, связанные с проверкой, а затем в дополнение к отлову общего, невзрачного ServiceException
. Это кажется странным, потому что ServiceException
настолько абстрагирован, что невозможно с первого взгляда определить, что пошло не так, вам придется проверять журнал и читать сообщение об исключении или перемещаться по иерархии вызовов в коде. Тогда я прошу убедиться, что это действительно хорошая практика, обычная или разумная практика - смешивать эти два подхода, потому что интуитивно мне это кажется странным. Есть ли какие-либо параллели в ядре Java, где оба подхода к обработке исключений используются в одном методе?
Возможное решение (согласно ответу Андреаса)
Имеет ли смысл обрабатывать подобные исключения в моем классе Service? Ниже приведен псевдокод, представляющий возможное решение. Таким образом, я не создаю бесполезное, проверенное, невзрачное исключение ServiceException, но у меня есть универсальное средство для непроверенных исключений (путем перехвата исключения в конце). Что вы думаете об этом решении?
class HttpService {
private Service service;
// ...
public HttpServiceResponse saveTree(Node root) {
try {
service.saveTree(root);
} catch (DuplicateNodeException e) {
return HttpServiceResponse.failure(DUPLICATE_NODE_EXCEPTION);
} catch (MissingXException e) {
return HttpServiceResponse.failure(MISSING_X_EXCEPTION);
} catch (MissingYException e) {
return HttpServiceResponse.failure(MISSING_Y_EXCEPTION);
} catch (MissingZException e) {
return HttpServiceResponse.failure(MISSING_Z_EXCEPTION);
} catch (Exception e) {
return HttpServiceResponse.failure(INTERNAL_SERVER_ERROR);
}
}
}
@AndyTurner Спасибо, это хороший совет. Вы проголосовали против? Если да, то почему?
Я не голосовал против.
Рассматривали ли вы определение этих исключений как неотмеченные и позволяли ли клиенту решать, какие из них обрабатывать, а какие игнорировать.
@tsolakp Да, есть. Но исключение, которое я пытаюсь обработать надлежащим образом, - это исключение DataAccessException (не отмеченное), созданное JdbcTemplate в моем классе DAO. Теперь, если оба уровня диспетчера и службы генерируют исключение DataAccessException, это не соответствует абстракции, согласно совету Энди Тернера. И если бы класс DAO переключился на файл или веб-службу, то внезапно мне пришлось бы перехватывать исключение IOException или RemoteException на уровнях диспетчера и службы. Вот почему я решил абстрагироваться от него как ManagerException на уровне менеджера и ServiceException на уровне сервиса.
В то время как JDBC API выдает проверилSQLException
, JdbcTemplate
обертывает их с помощью не отмеченDataAccessException
, поэтому вызывающим абонентам не нужно специально объявлять и обрабатывать их. Нет необходимости оборачивать DataAccessException
ServiceException
. Если служба выполняет другие действия, которые вызывают исключения проверил, они должны быть аналогичным образом обернуты конкретными исключениями, а не общим ServiceException
. Вызывающий не заботится об этих исключениях, потому что вызывающий ничего не может с ними поделать, но не просто переносите все исключения в общий ServiceException
.
@Andreas Для меня это имеет смысл. Я обновил свой вопрос, указав возможное решение. Что вы думаете об этом решении? Это согласуется с тем, что вы говорите?
@ ktm5124 Не отмечено ли исключение DuplicateNodeException
или MissingXException
? Если да, то зачем обрабатывать и оборачивать их HttpServiceResponse.failure
?
@tsolakp DuplicateNodeException
и MissingXException
- отмеченные исключения. Я оборачиваю их HttpServiceResponse.failure, чтобы я мог вернуть интеллектуальное сообщение об ошибке в вызов AJAX, который вызывает мою службу.
@ ktm5124 Я бы объявил их отключенными, если вы не можете изменить их исходный код. Обычно вы редко определяете пользовательские проверенные исключения.
@tsolakp Но я хочу, чтобы вызывающая сторона действовала в соответствии с ними, поскольку это восстанавливаемые исключения, поэтому имеет смысл объявить их как отмеченные, не так ли? См. Десять советов в конце этой статьи: blog.takipi.com/….
Обертывание исключения наверняка не скрывает подробностей того, что пошло не так. Вся цель цепочки исключений - сохранить базовое исключение. Поэтому обертывание базовых исключений в тип исключения, подходящий для вашей службы, является очень хорошей практикой. (Обратите внимание, что ваш примерный код не делает этого, а вместо этого отбрасывает исключения, которые он улавливает, что, на мой взгляд, делает это плохой практикой.)
Большинство исключений должно быть не отмечен, потому что они на самом деле не являются действенными, то есть вызывающий действительно ничего не может с этим поделать, кроме как передать его или зарегистрировать его и не выполнить любую операцию, которая в настоящее время выполняется. Следует проверять только те исключения, которые являются действенными и требуют такого действия вызывающего объекта, что довольно редко.
Если все исключения не отмечены, это означает, что вызывающий абонент может справиться с одним универсальным запросом. Поскольку на уровне обслуживания больше не требуется обертывать исключения из, например, на уровне DAO вызывающий жестяная банка при желании перехватывает и обрабатывает определенные исключения.
Совет Эффективная Java - генерировать исключения, соответствующие абстракции.