是否可以使用异常而不是详细的空检查?

Mis*_*ith 12 c# java null exception

我在项目中遇到了这个问题:有一系列嵌套对象,例如:类A包含一个B类的实例变量,它实际上有一个类C的实例变量,......,直到我们有一个节点Z级树.

     -----      -----      -----      -----               ----- 
     | A | ---> | B | ---> | C | ---> | D | ---> ... ---> | Z |
     -----      -----      -----      -----               -----  
Run Code Online (Sandbox Code Playgroud)

每个类为其成员提供getter和setter.父A实例由XML解析器创建,链中的任何对象都为空是合法的.

现在假设在应用程序的某个点上,我们引用了一个A实例,并且只有它包含一个Z对象时,我们必须在它上面调用一个方法.使用定期检查,我们得到以下代码:

    A parentObject;

    if(parentObject.getB() != null &&
        parentObject.getB().getC() != null &&
        parentObject.getB().getC().getD() != null &&
        parentObject.getB().getC().getD().getE() != null &&
        ...
        parentObject.getB().getC().getD().getE().get...getZ() != null){
            parentObject.getB().getC().getD().getE().get...getZ().doSomething();
    }
Run Code Online (Sandbox Code Playgroud)

我知道异常不应该用于普通的控制流,但是我看到一些程序员这样做,而不是以前的代码:

    try {
        parentObject.getB().getC().getD().getE().get...getZ().doSomething();
    } catch (NullPointerException e){}
Run Code Online (Sandbox Code Playgroud)

这段代码的问题在于它在维护时可能会混淆,因为它没有清楚地显示允许哪些对象为null.但另一方面更简洁,更不"伸缩".

这样做可以节省开发时间吗?如何重新设计API以避免此问题?

我唯一能想到的就是避免isValid长空检查是为了提供嵌套对象的void实例并为它们中的每一个提供方法,但这不会在内存中创建很多不必要的对象吗?

(我使用过Java代码,但同样的问题可以应用于C#属性)

谢谢.

joe*_*rgl 16

如果parentObject需要知道A包含一个包含C的B,那么这是一个糟糕的设计....这样,一切都与所有东西相连.你应该看一下demeter法则:http://en.wikipedia.org/wiki/Law_Of_Demeter

parentObject应该只调用其实例变量B上的方法.因此,B应该提供一个允许决策的方法,例如

public class A {
  private B myB;
  //...
  public boolean isItValidToDoSomething(){
    if(myB!=null){
      return myB.isItValidToDoSomething();
    }else{
      return false;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

最终,在Z级别,该方法必须返回true.

Imho,节省开发时间绝不是容忍设计问题的理由.这些问题迟早会比你先解决问题所花费的时间更多

  • 它还将使用除了委托给类成员之外什么都不做的方法来填充一些更高级别的类.现在,如果您发现程序中的许多类都混杂了isValidSomething()方法等等,您可能需要重新考虑合成策略.例如,如果A确实需要在Z上调用方法,那么为什么Z不是A的实例成员? (2认同)

Pau*_*ane 3

就我个人而言,我喜欢通过使用选项类型来完全避免这个问题。通过调整从这些方法/属性返回的值,而Option<T>不是T调用者可以选择他们希望如何处理没有值的情况。

选项类型可以有包含值,也可以没有(但选项本身永远不能为空),但调用者不能简单地传递它而不解开值,因此它迫使调用者处理可能没有值的事实。

例如在 C# 中:

class A {
    Option<B> B { get { return this.optB; } }
}

class B {
    Option<C> C { get { return this.optC; } }
}

// and so on
Run Code Online (Sandbox Code Playgroud)

如果调用者想要抛出,他们只是检索值而不显式检查是否有:

A a = GetOne();
D d = a.Value.B.Value.C.Value.D.Value; // Value() will throw if there is no value
Run Code Online (Sandbox Code Playgroud)

如果调用者想要在任何步骤没有值的情况下默认,他们可以执行映射/绑定/投影:

A a = GetOne();
D d = a.Convert(a => a.B) // gives the value or empty Option<B>
       .Convert(b => b.C) // gives value or empty Option<C>
       .Convert(c => c.D) // gives value or empty Option<D>
       .ValueOrDefault(new D("No value")); // get a default if anything was empty 
Run Code Online (Sandbox Code Playgroud)

如果调用者希望在每个阶段都默认,他们可以:

A a = GetOne();
D d = a.ValueOrDefault(defaultA)
     .B.ValueOrDefault(defaultB)
     .C.ValueOrDefault(defaultC)
     .D.ValueOrDefault(defaultD);
Run Code Online (Sandbox Code Playgroud)

Option目前还不是 C# 的一部分,但我想有一天会成为的。您可以通过引用 F# 库来获取实现,也可以在 Web 上找到实现。如果您想要我的,请告诉我,我会将其发送给您。