我们正在构建一个 Java SDK 来简化对我们提供 REST API 的服务之一的访问。此 SDK 供第 3 方开发人员使用。我正在努力寻找在 SDK 中实现更适合 Java 语言的错误处理的最佳模式。
假设我们有其余端点:GET /photos/{photoId}
. 这可能会返回以下 HTTP 状态代码:
- 401:用户未通过身份验证
- 403 : 用户没有权限访问这张照片
- 404:没有那个id的照片
该服务看起来像这样:
interface RestService {
public Photo getPhoto(String photoID);
}
在上面的代码中,我还没有解决错误处理。我显然想为 sdk 的客户端提供一种方法来了解发生了哪个错误,并可能从中恢复。Java 中的错误处理是使用异常来完成的,所以让我们继续吧。但是,使用异常执行此操作的最佳方法是什么?
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 异常呢?如果我们更改 RestServiceException 以扩展 RuntimeException:
public class RestServiceException extends RuntimeException
我们保留方法的签名:
public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
这样,我可以在不破坏现有代码的情况下向方法的签名添加新的异常。但是,使用此解决方案,开发人员不会被迫捕获任何异常,并且在他仔细阅读文档(是的,正确的!)或注意到方法签名中的异常之前,不会注意到他需要处理的错误情况.
在这种情况下,错误处理的最佳做法是什么?
除了我提到的那些,还有其他(更好的)替代品吗?