Мы строим Java SDK, чтобы упростить доступ к одной из наших сервисов, которые предоставляют REST API. Этот SDK будет использоваться сторонними разработчиками. Я изо всех сил пытаюсь найти лучший образец для реализации обработки ошибок в SDK, который лучше подходит для языка Java.
Скажем, у нас есть остальная конечная точка: GET /photos/{photoId}
.
Это может вернуть следующие коды состояния HTTP:
- 401: Пользователь не аутентифицирован
- 403: у пользователя нет разрешения на доступ к этой фотографии.
- 404: Нет фотографии с этим id
Служба выглядит примерно так:
interface RestService {
public Photo getPhoto(String photoID);
}
В приведенном выше коде я пока не обращаюсь к обработке ошибок. Я, очевидно, хочу предоставить способ для клиента sdk узнать, какая ошибка произошла, чтобы потенциально восстановить его. Обработка ошибок в Java выполняется с использованием Exceptions, поэтому отпустите это. Однако, каков наилучший способ сделать это, используя исключения?
1. Имейте единственное исключение с информацией об ошибке.
public Photo getPhoto(String photoID) throws RestServiceException;
public class RestServiceException extends Exception {
int statusCode;
...
}
Клиент sdk может тогда сделать что-то вроде этого:
try {
Photo photo = getPhoto("photo1");
}
catch(RestServiceException e) {
swtich(e.getStatusCode()) {
case 401 : handleUnauthenticated(); break;
case 403 : handleUnauthorized(); break;
case 404 : handleNotFound(); break;
}
}
Однако мне не очень нравится это решение в основном по двум причинам:
- Изучив подпись метода, разработчик понятия не имеет, какие ошибки могут возникнуть у него.
- Разработчик должен иметь дело непосредственно с кодами состояния HTTP и знать, что они означают в контексте этого метода (очевидно, если они правильно используются, то много раз, когда значение известно, однако это может быть не всегда случай).
2. Имейте иерархию классов ошибок
Подпись метода остается:
public Photo getPhoto(String photoID) throws RestServiceException;
Но теперь мы создаем исключения для каждого типа ошибки:
public class UnauthenticatedException extends RestServiceException;
public class UnauthorizedException extends RestServiceException;
public class NotFoundException extends RestServiceException;
Теперь клиент SDK мог бы сделать что-то вроде этого:
try {
Photo photo = getPhoto("photo1");
}
catch(UnauthenticatedException e) {
handleUnauthorized();
}
catch(UnauthorizedException e) {
handleUnauthenticated();
}
catch(NotFoundException e) {
handleNotFound();
}
При таком подходе разработчику не нужно знать о кодах состояния HTTP, которые генерировали ошибки, он должен обрабатывать только исключения Java. Еще одно преимущество заключается в том, что разработчик может улавливать только те исключения, которые он хочет обработать (в отличие от предыдущей ситуации, когда ему нужно было бы поймать единственное исключение (RestServiceException
) и только потом решить, хочет ли он с этим справиться или нет).
Однако есть еще одна проблема. Посмотрев на подпись метода, разработчик до сих пор не знает, какие ошибки он может потребовать, потому что у нас есть только суперкласс в сигнатуре метода.
3. Имейте иерархию классов ошибок + перечислите их в сигнатуре метода
Хорошо, так что теперь приходит в голову изменить сигнатуру метода на:
public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
Однако, возможно, что в будущем в эту конечную точку отдыха могут быть добавлены новые ситуации с ошибкой. Это означало бы добавление нового исключения в подпись метода, и это было бы изменением в java api. Мы хотели бы иметь более надежное решение, которое не привело бы к нарушению изменений в api в описанной ситуации.
4. Имейте иерархию классов ошибок (с использованием исключений Unchecked) + перечислите их в сигнатуре метода
Итак, как насчет исключений Unchecked? Если мы изменим исключение RestServiceException, чтобы расширить исключение RuntimeException:
public class RestServiceException extends RuntimeException
И мы сохраняем подпись метода:
public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
Таким образом, я могу добавить новые исключения к сигнатуре метода, не нарушая существующий код. Тем не менее, с помощью этого решения разработчик не вынужден улавливать какое-либо исключение и не замечает, что ему приходится обрабатывать ошибки, пока он внимательно не прочитает документацию (да, правильно!) Или не заметил Исключения, которые находятся в подписи метода.
Какая наилучшая практика для обработки ошибок в таких ситуациях?
Есть ли другие (лучшие) альтернативы тем, о которых я говорил?