如何避免使用exception进行流量控制?
我被分配了一个项目来开发一组类,这些类充当存储系统的接口。 要求是该类支持具有以下签名的get方法:
public CustomObject get(String key, Date ifModifiedSince)
基本上,当且仅当在ifModifiedSince
之后修改了对象时,该方法才会返回与key
关联的CustomObject
。 如果存储系统不包含key
则该方法应返回null。
我的问题是:
如何处理密钥存在但未修改对象的方案?
这很重要,因为使用此类的一些应用程序将是Web服务和Web应用程序。 这些应用程序需要知道是返回404(未找到),304(未修改)还是200(OK,这是数据)。
我正在权衡的解决方案是:
- 当存储系统不包含
key
时,抛出自定义exception -
ifModifiedSince
失败时抛出自定义exception。 - 将状态属性添加到CustomObject。 要求来电者检查财产。
我对这三个选项中的任何一个都不满意。 我不喜欢选项1和2,因为我不喜欢使用流控制的exception。 当我的目的是表明没有价值时,我也不喜欢返回一个值 。
尽管如此,我倾向于选择3。
有没有我不考虑的选择? 有没有人对这三种选择中的任何一种都有强烈的感受?
这个问题的答案,转述:
- 提供一个
contains
方法并要求调用者在调用get(key, ifModifiedSince)
之前调用它,如果key不存在则抛出exception,如果没有修改object则返回null。 - 将响应和数据(如果有)包装在复合对象中。
- 使用预定义常量表示某个状态(
UNMODIFIED, KEY_DOES_NOT_EXIST
)。 - 调用者实现了用作回调的接口。
- 设计很糟糕。
为什么我不能选择答案#1
我同意这是理想的解决方案,但我已经(不情愿地)被解雇了。 这种方法的问题在于,在大多数使用这些类的情况下,后端存储系统将是第三方远程系统,如Amazon S3。 这意味着contains
方法需要往返存储系统,在大多数情况下会进行另一次往返。 因为这会花费时间和金钱 ,所以不是一种选择。
如果不是因为这个限制,这将是最好的方法。
(我意识到我没有在这个问题中提到这个重要元素,但我试图保持简短。显然它是相关的。)
结论:
在阅读完所有答案后,我得出的结论是,在这种情况下,包装器是最好的方法。 基本上我会模仿HTTP,包括响应代码和内容正文(消息)的元数据(标题)。
听起来你实际上想要返回两个项目:响应代码和找到的对象。 您可以考虑创建一个包含两者并将它们一起返回的轻量级包装器。
public class Pair{ public K first; public V second; }
然后,您可以创建一个包含响应代码和数据的新对。 作为使用generics的副作用,您可以将此包装重用于您实际需要的任何对。
此外,如果数据尚未过期,您仍然可以返回它,但是给它一个303代码,让他们知道它没有变化。 4xx系列将与null
配对。
根据给定的要求,您无法执行此操作。
如果您设计了合同 ,则添加条件并调用调用者
exists(key): bool
服务实现如下所示:
if (exists(key)) { CustomObject o = get(key, ifModifiedSince); if (o == null) { setResponseCode(302); } else { setResponseCode(200); push(o); } } else { setResponseCode(400); }
客户端保持不变,从未注意到您已经预先validation过。
如果你没有设计合同可能有一个很好的理由,或者可能只是设计师(或建筑师)的错误。 但既然你无法改变它,那么你也不必担心。
那么你应该遵守规范并继续这样:
CustomObject o = get(key, ifModifiedSince); if (o != null) { setResponseCode(200); push(o); } else { setResponseCode(404); // either not found or not modified. }
好的,在这种情况下你不会发送302,但可能就是它的设计方式。
我的意思是,出于安全原因,服务器不应该返回更多信息[探测器获取(键,日期)只返回null或object]
所以不要担心。 与您的经理交谈,让他知道这个决定。 用这个决定评论代码。 如果你有建筑师手中确认这个奇怪的限制背后的理由。
你可能没有看到这一点,他们可以根据你的建议修改合同。
有时候,在想要正确行事的同时,我们可能会出错并损害我们应用的安全性。
与您的团队沟通。
您可以创建一个特殊的最终CustomObject作为“标记”以表示未更改:
static public final CustomObject UNCHANGED=new CustomObject();
并使用“==”而不是.equals()测试匹配项。
它也可能在未更改时返回null并且抛出exception不存在? 如果我必须选择你的3中的一个,我会选择1,因为这似乎是最特殊的情况。
寻找一个不存在的对象对我来说似乎是一个特例。 与允许调用者确定某个对象是否存在的方法相结合,我认为如果不存在则抛出该exception是可以的。
public bool exists( String key ) { ... }
来电者可以这样做:
if (exists(key)) { CustomObject modified = get(key,DateTime.Today.AddDays(-1)); if (modified != null) { ... } } or try { CustomObject modified = get(key,DateTime.Today.AddDays(-1)); } catch (NotFoundException) { ... }
exception的问题是它们意味着由于exception和exception行为而发出“快速失败”情况(即,如果未处理,则exception将停止应用程序)。
我不认为“密钥存在但对象未被修改的情况”是一个例外,当然不是一个exception的。
因此我不会使用exception,而是我会记录调用者为了正确解释结果(属性或特殊对象)而需要执行的操作。
对该方法签名的要求有多严格?
看起来您正在开发一个仍在进行中的项目。 如果您class级的消费者是其他开发人员,您能说服他们他们要求的方法签名是否不足? 也许他们还没有意识到应该存在两种独特的故障模式(密钥不存在且对象未被修改)。
如果这是一个选项,我会与你的主管讨论。
我仍然会返回null。
该属性的意图是返回在指定日期之后修改的对象。 如果没有对象返回null是可以的,那么确定为未修改的对象返回null也是可以的。
我个人会为未修改的对象返回null,并为不存在的对象抛出exception。 这似乎更自然。
你没有使用流量控制BTW的exception是正确的,所以如果你只有这3个选项,你的直觉是正确的。
您可以遵循.Net库模式,并在自定义对象中有一个名为CustomObject.Empty的公共静态只读字段,该字段的类型为CustomObject (如string.Empty和Guid.Empty)。 如果未修改对象,则可以返回此值(函数使用者需要与之进行比较)。
编辑: 我只是发现你在Java工作,但原则仍然适用
这为您提供了以下选项
-
如果密钥不存在,则返回null。
-
如果密钥存在但尚未修改对象,则返回CustomObject.Empty 。
缺点是消费者需要知道null返回值和CustomObject.Empty返回值之间的差异。
也许该属性更恰当地称为CustomObject.NotModified,因为Empty实际上是用于Value类型,因为它们不能为null。 NotModified也会更容易向消费者传达该领域的含义。
关于要求的(预期)界面严重破坏。 你尝试在一种方法中做不相关的事情。 这是软件地狱之路。
提供一个Callback作为Callback类可以是事件驱动或setter驱动的参数。
您有类的接口定义可能发生的各种错误,如果需要,将CustomObject作为事件的参数传递。
public interface Callback { public void keyDoesNotExist(); public void notModified(CustomObject c); public void isNewlyModified(CustomObject c); . . . }
通过这种方式,您允许Callback接口的实现者定义事件发生时要执行的操作,并且您可以通过接口选择是否需要传递检索到的对象。 最后,它降低了返回时逻辑的复杂性。 你的方法做了一次。 API的实现者根本不需要这样做,因为它是为它们完成的。
如果可以接受,您可以返回一个放大的CustomObject(包装器),其中包含表示对象及其修改状态的值(如果有)等。