我们正在构建一个 Java SDK,以简化对提供 REST API 的服务之一的访问。该SDK供第三方开发者使用。我正在努力寻找在 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;
但是,将来可能会向此休息端点添加新的错误情况。这意味着向方法的签名添加一个新的 Exception,这将是对 java api 的重大更改。我们希望有一个更强大的解决方案,不会导致在所描述的情况下对 api 进行重大更改。
4. 拥有错误的类层次结构(使用未经检查的异常)+将它们列在方法的签名中
那么,未经检查的异常呢?如果我们更改 RestServiceException 以扩展 RuntimeException:
public class RestServiceException extends RuntimeException
我们保留方法的签名:
public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
这样我就可以向方法的签名添加新的异常,而不会破坏现有代码。
但是,使用此解决方案,开发人员不会被迫捕获任何异常,也不会注意到需要处理的错误情况,直到他仔细阅读文档(是的,对!)或注意到方法签名中的异常。
在这种情况下处理错误的最佳实践是什么?
除了我提到的那些之外,还有其他(更好的)替代方案吗?