假设我们有rest端点:
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;
然而,未来可能会为此rest端点添加新的错误情况。这意味着需要将新的异常添加到该方法的签名中,这将是对Java API的破坏性更改。我们希望有一个更健壮的解决方案,不会导致API在描述的情况下发生破坏性变化。
4.使用错误的类层次结构(使用未经检查的异常)+在方法的签名中列出它们
那么,未经检查的异常怎么样?如果我们将RestServiceException更改为扩展RuntimeException:
public class RestServiceException extends RuntimeException
同时我们保留方法的签名:
public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
这样做可以在不破坏现有代码的情况下向方法签名添加新的异常。但是,采用此解决方案,开发人员不被强制捕获任何异常,也不会注意到需要处理的错误情况,直到他仔细阅读文档(是的,没错!)或注意到方法签名中的异常。
这种情况下的最佳错误处理实践是什么?
是否有其他(更好的)替代方案?