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

时间:2008-12-06 00:34:29

标签: java exception return-type control-flow

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

public CustomObject get(String key, Date ifModifiedSince)

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

我的问题是:

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

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

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

  1. 当出现自定义异常时 存储系统不包含 key
  2. 当出现自定义异常时 ifModifiedSince失败。
  3. 将状态属性添加到CustomObject。要求来电者查看财产。
  4. 我对这三个选项中的任何一个都不满意。我不喜欢选项1和2,因为我不喜欢使用流控制的异常。当我的目的是表明没有值时,我也不喜欢返回值。

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

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


    本问题的答案,转述:

    1. 提供contains 方法并要求调用者调用它 在致电get(key, ifModifiedSince)之前,扔掉 例如,如果密钥不存在, 如果对象尚未返回null 修改。
    2. 包装响应和数据(如果有) 在复合对象中。
    3. 使用预定义常量表示某个州(UNMODIFIED, KEY_DOES_NOT_EXIST)。
    4. 调用者实现接口 用作回调。
    5. 设计很糟糕。

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

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

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

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


      结论:

      在阅读完所有答案之后,我得出的结论是,在这种情况下,包装器是最好的方法。基本上我会模仿HTTP,包括响应代码和内容正文(消息)的元数据(标题)。

11 个答案:

答案 0 :(得分:7)

听起来你真的想要返回两个项目:响应代码和找到的对象。您可以考虑创建一个包含两者并将它们一起返回的轻量级包装器。

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

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

此外,如果数据尚未过期,您仍然可以返回它,但是给它一个303代码,让他们知道它没有变化。 4xx系列将与null配对。

答案 1 :(得分: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,但可能就是它的设计方式。

我的意思是,出于安全原因,服务器不应该返回比更多的信息[探测器获取(密钥,日期)只返回null或object]

所以不要担心。与您的经理交谈,让他知道这个决定。用这个决定评论代码。如果你有建筑师手中确认这个奇怪的限制背后的理由。

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

有时候,如果想要正确行事,我们可能会出错并损害我们应用的安全性。

与您的团队沟通。

答案 2 :(得分:3)

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

static public final CustomObject UNCHANGED=new CustomObject();

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

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

答案 3 :(得分: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) { ... }

答案 4 :(得分:2)

异常的问题是它们意味着发出“快速失败”的情况(即如果没有处理,例外将 停止 一个应用程序) 和异常行为。

我不认为“密钥存在但对象未被修改的情况”是一个例外情况,当然不是一个异常情况。

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

答案 5 :(得分:1)

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

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

如果可以选择,我会与你的主管讨论。

答案 6 :(得分:1)

我仍然会返回null。

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

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

你没有使用流量控制BTW的异常,所以如果你只有这3个选项,那么你的直觉是正确的。

答案 7 :(得分:1)

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

这为您提供以下选项

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

  • 如果密钥存在但尚未修改对象,则返回 CustomObject.Empty

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

也许该属性更适合称为 CustomObject.NotModified ,因为Empty实际上是用于Value类型,因为它们不能为null。此外, NotModified 会更容易向消费者传达该领域的含义。

答案 8 :(得分:1)

关于要求的(预期)界面严重受损。你尝试在一种方法中做不相关的事情。这是软件地狱之路。

答案 9 :(得分:1)

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

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

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

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

答案 10 :(得分:0)

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