【问题标题】:How Can I Avoid Using Exceptions for Flow Control?如何避免使用异常进行流控制?
【发布时间】:2008-12-06 00:34:28
【问题描述】:

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

public CustomObject get(String key, Date ifModifiedSince)

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

我的问题是这样的:

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

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

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

  1. 当 存储系统不包含 key
  2. ifModifiedSince 失败。
  3. 向 CustomObject 添加状态属性。要求调用者检查属性。

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

不过,我倾向于选项 3。

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


此问题的答案,意译:

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

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

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

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

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


结论:

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

【问题讨论】:

    标签: java exception return-type control-flow


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 这还不错,但这应该是强类型的,不需要泛型。
    • Pair 类型不能再强类型化了,为什么你不想让事情尽可能通用呢?多态是个好东西。
    • 您可以返回找到的对象或失败代码,而不是返回找到的对象和响应代码。这可以使用不相交的联合类型(如 Either.)使其成为类型安全的
    • 措辞选择不当。是的,它是强类型的,但是“Pair”是模糊的。对什么?是的,您指定了“Pair”,但是 Integer 是什么意思?有通用,然后有混乱。希望至少在那时,代码是在包装器或其他东西中输入的。
    【解决方案2】:

    根据给定的要求,您无法做到这一点。

    如果你设计了合约,那么添加一个条件,让调用者调用

    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]

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

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

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

    与您的团队沟通。

    【讨论】:

    • 404 == 未找到,304 == 未修改。如果一个结果意味着两种不同的东西,那总有一天会咬人的。
    • 是的,但也许这就是目的。例如。登录到雅虎!用假账户。服务器说:“无效的 ID 或密码。”他们没有说“该帐户不存在”这是有充分理由的。雅虎不会告诉您帐户是否存在以及密码是否错误。即使他们可以。
    • 这可能是企业安全顾问强加的安全约束(我们都有一个对吗?)哦,也许,设计师只是忘记了这个场景(这永远不会发生对吗?设计总是正确的) 我们不知道。可能这个服务是在内部使用的,现在是 pub
    【解决方案3】:

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

    static public final CustomObject UNCHANGED=new CustomObject();
    

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

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

    【讨论】:

    • 这确实引入了一系列 if,then,else 检查对这个方法的每次调用都必须处理。使用例外几乎比使用例外更好,因为至少它们是强制执行的,并且更有条理。
    • 是的,但是异常会引入一系列try/catch/finally,所以净收益只是强制执行,以牺牲性能为代价?并且异常处理更冗长,离因果代码更远。所以......这取决于...... OP询问如何避免异常。
    【解决方案4】:

    寻找一个不存在的对象对我来说似乎是个例外。再加上一个允许调用者判断一个对象是否存在的方法,我觉得不存在的时候抛出异常就可以了。

    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) { ... }
    

    【讨论】:

      【解决方案5】:

      异常的问题在于,它们旨在发出“快速失败”场景的信号(即,如果未处理,异常将停止应用程序)由于异常和异常行为。

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

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

      【讨论】:

      • 是的,在这种情况下,例外是一个糟糕的决定。
      • 密钥从何而来?如果您有密钥,则该对象应该存在,如果不存在,则属于例外情况。
      • @Buzzer: 可能是这样,但是如果您的异常未检查,应用程序是否必须 stop ?我不这么认为。
      • 然后检查异常。未经检查的异常在当前级别是不可恢复或不可处理的。它们可以被提升到一个通知某人事件的层,然后等到采取行动。例外不是最好的解决方案,但在这种情况下是可行的。
      【解决方案6】:

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

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

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

      【讨论】:

        【解决方案7】:

        我仍然会返回 null。

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

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

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

        【讨论】:

        • 这是唯一合理的解决方案。如果不满足该方法的限定符,那么它应该返回 null,我看不出其他任何意义。无效键上的 null 是有问题的,并且可以采用任何一种方式。 Map 会返回 null,其他类会抛出 IllegalArgumentException。
        • 当然无效的key case已经确定了,所以两者都为null。
        【解决方案8】:

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

        这为您提供了以下选项

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

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

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

        也许该属性更恰当地称为 CustomObject.NotModified,因为 Empty 真正适用于 Value 类型,因为它们不能为空。此外,NotModified 可以更轻松地向消费者传达字段的含义。

        【讨论】:

          【解决方案9】:

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

          【讨论】:

          • 阿门!而且没有办法从那里回来。
          【解决方案10】:

          提供一个回调作为参数,回调类可以是事件驱动的,也可以是设置器驱动的。

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

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

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

          【讨论】:

            【解决方案11】:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2016-03-21
              • 1970-01-01
              • 1970-01-01
              • 2012-12-31
              • 1970-01-01
              • 2017-02-10
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多