代码之家  ›  专栏  ›  技术社区  ›  Benry

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

  •  9
  • Benry  · 技术社区  · 17 年前

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

    public CustomObject get(String key, Date ifModifiedSince)
    

    基本上,该方法应该返回 CustomObject key 如果且仅当对象在以下时间被修改 ifModifiedSince 。如果存储系统不包含 钥匙 那么该方法应该返回null。

    我的问题是:

    如何处理键存在但对象具有 被修改了?

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

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

    1. 当发生以下情况时,抛出自定义异常 存储系统不包含 钥匙
    2. 当发生以下情况时,抛出自定义异常 如果修改开始 失败。
    3. 向CustomObject添加状态属性。要求调用者检查属性。

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

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

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


    这个问题的答案,释义:

    1. 提供a contains 方法并要求调用者调用它 打电话之前 get(key, ifModifiedSince) ,扔 如果密钥不存在,则出现异常, 如果对象尚未被访问,则返回null 被改进的。
    2. 包装响应和数据(如果有的话) 在复合对象中。
    3. 使用预定义的常量表示某个状态( UNMODIFIED, KEY_DOES_NOT_EXIST ).
    4. 呼叫者实现接口 用作回调。
    5. 这个设计糟透了。

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

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

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

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


    结论:

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

    11 回复  |  直到 17 年前
        1
  •  7
  •   James    17 年前

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

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

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

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

        2
  •  5
  •   Nis skaffman    11 年前

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

    如果你设计了合同 ,然后添加一个条件并使调用者调用

    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]

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

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

    有时,在想做对的时候,我们可能会做错,危及我们应用程序的安全性。

    与你的团队沟通。

        3
  •  3
  •   L. Cornelius Dol    17 年前

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

    static public final CustomObject UNCHANGED=new CustomObject();
    

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

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

        4
  •  3
  •   tvanfosson    17 年前

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

    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
  •  2
  •   VonC    17 年前

    异常的问题在于,它们意味着一种“快速失败”的情况(即如果不处理,异常将 停止 由于特殊情况 异常 行为。

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

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

        6
  •  1
  •   James Eichele Bernard Igiri    17 年前

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

    你似乎在做一个仍在进行中的项目。如果你的类的消费者是其他开发人员,你能让他们相信他们要求的方法签名是不够的吗?也许他们还没有意识到应该有两种独特的故障模式(密钥不存在,对象未被修改)。

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

        7
  •  1
  •   gbjbaanb    17 年前

    我仍然会返回null。

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

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

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

        8
  •  1
  •   Doctor Jones    17 年前

    你可以跟着。的网络库模式,并在名为的自定义对象中有一个公共静态只读字段 自定义对象。空的 那是典型的 自定义对象 (如string.Empty和Guid.Empty)。如果对象未被修改,则可以返回此值(函数使用者需要与之进行比较)。
    编辑: 我刚刚发现你在用Java工作,但这个原则仍然适用

    这为您提供了以下选项

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

    • 自定义对象。空的 如果密钥存在但对象未被修改。

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

    也许该物业的名称更恰当 自定义对象。未修改 因为Empty实际上是针对Value类型的,因为它们不能为null。而且 未修改 将更容易地向消费者传达该字段的含义。

        9
  •  1
  •   blablub blablub    17 年前

    关于需求的(预期)接口严重损坏。你试图用一种方法做无关的事情。这是通往软件地狱的道路。

        10
  •  1
  •   Spencer Kormos    17 年前

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

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

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

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

        11
  •  0
  •   eulerfx    17 年前

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

    推荐文章