如何避免使用exception进行流量控制?

我被分配了一个项目来开发一组类,这些类充当存储系统的接口。 要求是该类支持具有以下签名的get方法:

public CustomObject get(String key, Date ifModifiedSince) 

基本上,当且仅当在ifModifiedSince之后修改了对象时,该方法才会返回与key关联的CustomObject 。 如果存储系统不包含key则该方法应返回null。

我的问题是:

如何处理密钥存在但未修改对象的方案?

这很重要,因为使用此类的一些应用程序将是Web服务和Web应用程序。 这些应用程序需要知道是返回404(未找到),304(未修改)还是200(OK,这是数据)。

我正在权衡的解决方案是:

  1. 当存储系统不包含key时,抛出自定义exception
  2. ifModifiedSince失败时抛出自定义exception。
  3. 将状态属性添加到CustomObject。 要求来电者检查财产。

我对这三个选项中的任何一个都不满意。 我不喜欢选项1和2,因为我不喜欢使用流控制的exception。 当我的目的是表明没有价值时,我也不喜欢返回一个

尽管如此,我倾向于选择3。

有没有我不考虑的选择? 有没有人对这三种选择中的任何一种都有强烈的感受?


这个问题的答案,转述:

  1. 提供一个contains方法并要求调用者在调用get(key, ifModifiedSince)之前调用它,如果key不存在则抛出exception,如果没有修改object则返回null。
  2. 将响应和数据(如果有)包装在复合对象中。
  3. 使用预定义常量表示某个状态( UNMODIFIED, KEY_DOES_NOT_EXIST )。
  4. 调用者实现了用作回调的接口。
  5. 设计很糟糕。

为什么我不能选择答案#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(包装器),其中包含表示对象及其修改状态的值(如果有)等。