9

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

public CustomObject get(String key, Date ifModifiedSince)

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

我的问题是这样的:

如何处理密钥存在但对象未修改的情况

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

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

  1. 当存储系统不包含 key
  2. 失败时抛出自定义异常 ifModifiedSince
  3. 将状态属性添加到 CustomObject。要求调用者检查属性。

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

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

有没有我不考虑的选项?有没有人对这三个选项中的任何一个有强烈的感觉?


这个问题的答案,意译:

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

为什么我不能选择答案#1

我同意这是理想的解决方案,但我已经(不情愿地)驳回了它。这种方法的问题在于,在使用这些类的大多数情况下,后端存储系统将是第三方远程系统,如 Amazon S3。这意味着一种contains方法将需要到存储系统的往返行程,在大多数情况下,这之后会进行另一次往返行程。因为这会花费时间和金钱,所以这不是一种选择。

如果没有这种限制,这将是最好的方法。

(我意识到我没有在问题中提到这个重要元素,但我试图保持简短。显然它是相关的。)


结论:

在阅读了所有答案后,我得出结论,在这种情况下,包装器是最好的方法。本质上,我将模仿 HTTP,使用元数据(标头)包括响应代码和内容主体(消息)。

4

11 回答 11

7

听起来您实际上想要返回两项:响应代码和找到的对象。您可能会考虑创建一个轻量级包装器,将它们保存在一起并将它们一起返回。

public class Pair<K,V>{
  public K first;
  public V second;
}

然后您可以创建一个新的 Pair 来保存您的响应代码和数据。作为使用泛型的副作用,您可以将这个包装器重用于您实际需要的任何对。

另外,如果数据还没有过期,你仍然可以返回它,但是给它一个 303 代码,让他们知道它没有改变。4xx 系列将与null.

于 2008-12-06T01:34:17.817 回答
5

根据给定的要求,您不能这样做。

如果您设计了合约,则添加一个条件并让调用者调用

exists(key): bool

服务实现如下所示:

if (exists(key)) {
    CustomObject o = get(key, ifModifiedSince);
    if (o == null) { 
      setResponseCode(302);
    } else {
      setResponseCode(200);
      push(o);
   }

} else {
      setResponseCode(400);
}

客户端保持不变,并且永远不会注意到您已经预先验证。

如果您没有设计合同可能有一个很好的理由,或者可能只是设计师(或建筑师)的错。但既然你不能改变它,那么你也不必担心。

然后你应该遵守规范并像这样进行:

 CustomObject o = get(key, ifModifiedSince);

 if (o != null) {
     setResponseCode(200);
     push(o);
  } else {
     setResponseCode(404); // either not found or not modified.
  }

好的,在这种情况下,您不会发送 302,但可能这就是它的设计方式。

我的意思是,出于安全原因,服务器不应返回更多信息[探针是 get(key, date) 仅返回 null 或 object ]

所以不用担心。和你的经理谈谈,让他知道这个决定。也用这个决定评论代码。如果你手头有建筑师确认这个奇怪限制背后的基本原理。

他们很有可能您没有看到这一点,他们可以根据您的建议修改合同。

有时,在想要正确进行的同时,我们可能会错误地进行并危及我们应用程序的安全性。

与您的团队沟通。

于 2008-12-06T01:08:46.527 回答
3

您可以创建一个特殊的最终 CustomObject 作为“标记”以指示未更改:

static public final CustomObject UNCHANGED=new CustomObject();

并使用“==”而不是 .equals() 来测试匹配。

它也可以在未更改的情况下返回 null 并在不存在的情况下引发异常?如果我必须选择您的 3 个中的一个,我会选择 1,因为这似乎是最特殊的情况。

于 2008-12-06T00:37:55.917 回答
3

寻找一个不存在的对象对我来说似乎是个例外。再加上一个允许调用者确定对象是否存在的方法,我认为当它不存在时抛出异常是可以的。

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) { ... }
于 2008-12-06T00:39:15.413 回答
2

异常的问题在于它们是为了表示由于异常 和异常行为而导致的“快速失败”场景(即,如果不处理,异常将停止应用程序)。

我不认为“密钥存在但对象没有被修改的场景”是异常的,当然也不是异常的。

因此我不会使用异常,而是我会记录调用者需要执行的操作以正确解释结果(属性或特殊对象)。

于 2008-12-06T00:45:56.697 回答
1

该方法签名的要求有多严格?

好像您正在处理一个仍在进行中的项目。如果你的类的消费者是其他开发者,你能说服他们他们要求的方法签名是不够的吗?也许他们还没有意识到应该有两种独特的失败模式(key 不存在并且 object 没有被修改)。

如果可以的话,我会和你的主管讨论。

于 2008-12-06T01:02:59.250 回答
1

我仍然会返回 null。

该属性的目的是返回在指定日期之后修改的对象。如果没有对象返回 null 是可以的,那么肯定为未修改的对象返回 null 也是可以的。

我个人会为未修改的对象返回 null,并为不存在的对象抛出异常。这似乎更自然。

顺便说一句,您不使用异常进行流量控制是完全正确的,所以如果您只有这 3 个选项,那么您的直觉是正确的。

于 2008-12-06T01:10:43.200 回答
1

您可以遵循 .Net 库模式,并在名为CustomObject.Empty的自定义对象中拥有一个公共静态只读字段,该字段属于CustomObject类型(如 string.Empty 和 Guid.Empty)。如果对象没有被修改,您可以返回它(函数使用者需要与它进行比较)。
编辑:我只是发现你正在使用 Java,但原则仍然适用

这为您提供了以下选项

  • 如果键不存在,则返回 null。

  • 如果键存在但对象未被修改,则返回CustomObject.Empty 。

缺点是消费者需要知道 null 返回值和 CustomObject.Empty 返回值之间的区别。

也许该属性更恰当地称为CustomObject.NotModified,因为 Empty 确实适用于 Value 类型,因为它们不能为空。NotModified 还可以更容易地向消费者传达该字段的含义。

于 2008-12-06T01:10:48.457 回答
1

关于需求的(预期的)界面被严重破坏。你试图用一种方法做不相关的事情。这是通往软件地狱的道路。

于 2008-12-06T02:10:43.030 回答
1

提供一个 Callback 作为参数,其中 Callback 类可以是事件驱动的,也可以是 setter 驱动的。

如果需要,您可以让类的接口定义可能发生的各种错误,并将 CustomObject 作为事件的参数传递。

public interface Callback {
  public void keyDoesNotExist();
  public void notModified(CustomObject c);
  public void isNewlyModified(CustomObject c);
  .
  .
  .
}

通过这种方式,您允许回调接口的实现者定义事件发生时要做什么,并且您可以通过接口选择该条件是否需要传递检索到的对象。最后,它降低了返回逻辑的复杂性。你的方法会这样做一次。API 的实现者根本不需要这样做,因为它已经为他们完成了。

于 2008-12-08T04:34:33.940 回答
0

如果可以接受,您可以返回一个放大的 CustomObject(包装器),其中包含表示对象及其修改状态(如果有)等的值。

于 2008-12-06T00:45:05.043 回答