【问题标题】:Null check chain vs catching NullPointerException空检查链与捕获 NullPointerException
【发布时间】:2016-10-23 23:04:05
【问题描述】:

Web 服务返回一个巨大的 XML,我需要访问它的深层嵌套字段。例如:

return wsObject.getFoo().getBar().getBaz().getInt()

问题是getFoo()getBar()getBaz()可能都返回null

但是,如果我在所有情况下都检查null,代码会变得非常冗长且难以阅读。此外,我可能会错过某些字段的检查。

if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();

可以写吗

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    return -1;
}

或者这会被视为反模式吗?

【问题讨论】:

  • 我不介意null 进行那么多检查,因为wsObject.getFoo().getBar().getBaz().getInt() 已经是代码异味了。阅读"Law of Demeter" 是什么,并希望相应地重构您的代码。然后null 检查的问题也将消失。并考虑使用Optional
  • 如何使用 XPath 并将其留给他们评估?
  • 那个代码很可能是wsdl2java生成的,不尊重得墨忒耳法则。

标签: java exception nullpointerexception null custom-error-handling


【解决方案1】:

捕捉NullPointerException真正有问题的事情,因为它们几乎可以在任何地方发生。很容易从错误中得到一个,偶然发现它并继续,好像一切正​​常,从而隐藏一个真正的问题。 处理起来非常棘手,所以最好完全避免。(例如,考虑自动拆箱 null Integer。)

我建议您改用Optional 类。当您想要处理存在或不存在的值时,这通常是最佳方法。

使用它,您可以像这样编写代码:

public Optional<Integer> m(Ws wsObject) {
    return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
        .map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
        .map(b -> b.getBaz())
        .map(b -> b.getInt());
        // Add this if you want to return null instead of an empty optional if any is null
        // .orElse(null);
        // Or this if you want to throw an exception instead
        // .orElseThrow(SomeApplicationException::new);
}

为什么是可选的?

对可能不存在的值使用Optionals 而不是null 会使这一事实对读者来说非常明显和清晰,并且类型系统将确保您不会意外忘记它。

您还可以更方便地访问使用这些值的方法,例如 maporElse


缺勤是有效还是错误?

但还要考虑中间方法返回 null 是否是有效结果,或者这是否是错误的标志。如果它总是一个错误,那么最好抛出异常而不是返回一个特殊值,或者让中间方法本身抛出异常。


也许还有更多的选择?

另一方面,如果中间方法中的缺失值有效,也许您也可以为它们切换到Optionals?

然后你可以像这样使用它们:

public Optional<Integer> mo(Ws wsObject) {
    return wsObject.getFoo()
        .flatMap(f -> f.getBar())
        .flatMap(b -> b.getBaz())
        .flatMap(b -> b.getInt());        
}

为什么不可选?

我能想到的不使用Optional 的唯一原因是,这是否在代码中对性能非常关键的部分,并且如果垃圾收集开销被证明是一个问题。这是因为每次执行代码时都会分配一些Optional 对象,而VM 可能无法优化这些对象。在这种情况下,您原来的 if 测试可能会更好。

【讨论】:

  • FClass::getBar 等会更短。
  • @BoristheSpider:可能有点。但我通常更喜欢 lambdas 而不是方法 refs,因为类名通常要长得多,而且我发现 lambdas 更容易阅读。
  • @Lii 很公平,但请注意,方法引用可能会快一点,因为 lambda 可能需要更复杂的编译时构造。 lambda 将需要生成 static 方法,这将导致非常小的惩罚。
  • @Lii 实际上,我发现方法引用更简洁、更具描述性,即使它们稍长一些。
  • @likejudo:第二个示例旨在说明如果getXXX 方法本身返回Optionals,而不是可为空的对象,代码的外观。在这种情况下,您必须使用 flatMap 而不是 map
【解决方案2】:

我建议考虑Objects.requireNonNull(T obj, String message)。您可以为每个异常构建带有详细消息的链,例如

requireNonNull(requireNonNull(requireNonNull(
    wsObject, "wsObject is null")
        .getFoo(), "getFoo() is null")
            .getBar(), "getBar() is null");

我建议您不要使用特殊的返回值,例如 -1。这不是 Java 风格。 Java 设计了异常机制来避免这种源自 C 语言的老式方式。

投掷NullPointerException 也不是最好的选择。您可以提供自己的异常(使其 checked 以保证它将由用户处理或 unchecked 以更简单的方式处理它)或使用特定异常您正在使用的 XML 解析器。

【讨论】:

  • Objects.requireNonNull 最终抛出 NullPointerException。所以这不会使情况与return wsObject.getFoo().getBar().getBaz().getInt() 有任何不同
  • @ArkaGhosh,它也避免了很多 ifs 就像 OP 显示的那样
  • 这是唯一明智的解决方案。所有其他人都建议使用异常进行流控制,这是一种代码味道。附带说明:我认为 OP 完成的方法链接也是一种气味。如果他使用三个局部变量和相应的 if 情况,情况就会清楚得多。此外,我认为问题不仅仅是围绕 NPE 工作:OP 应该问自己为什么 getter 可以返回 null。空是什么意思?也许一些空对象会更好?或者在一个 getter 中崩溃并出现有意义的异常?基本上一切都比流控制的例外要好。
  • 使用异常来表示没有有效返回值的无条件建议不是很好。当方法以调用者难以恢复的方式失败并且在程序的某些其他部分中的 try-catch 语句中更好地处理时,异常很有用。为了简单地表示没有返回值,最好使用Optional 类,或者返回一个可为空的Integer
【解决方案3】:
return wsObject.getFooBarBazInt();

通过应用得墨忒耳法则,

class WsObject
{
    FooObject foo;
    ..
    Integer getFooBarBazInt()
    {
        if(foo != null) return foo.getBarBazInt();
        else return null;
    }
}

class FooObject
{
    BarObject bar;
    ..
    Integer getBarBazInt()
    {
        if(bar != null) return bar.getBazInt();
        else return null;
    }
}

class BarObject
{
    BazObject baz;
    ..
    Integer getBazInt()
    {
        if(baz != null) return baz.getInt();
        else return null;
    }
}

class BazObject
{
    Integer myInt;
    ..
    Integer getInt()
    {
        return myInt;
    }
}

【讨论】:

    【解决方案4】:

    你的方法很长,但可读性很强。如果我是一个新的开发人员来到你的代码库,我可以很快地看到你在做什么。大多数其他答案(包括捕获异常)似乎并没有让事情变得更具可读性,而在我看来,有些答案的可读性降低了。

    鉴于您可能无法控制生成的源代码并假设您确实只需要在这里和那里访问一些深度嵌套的字段,那么我建议您使用方法包装每个深度嵌套的访问。

    private int getFooBarBazInt() {
        if (wsObject.getFoo() == null) return -1;
        if (wsObject.getFoo().getBar() == null) return -1;
        if (wsObject.getFoo().getBar().getBaz() == null) return -1;
        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    

    如果你发现自己编写了很多这样的方法,或者你发现自己很想创建这些公共静态方法,那么我会创建一个单独的对象模型,按照你的意愿嵌套,只包含你关心的字段,然后转换从 Web 服务对象模型到您的对象模型。

    当您与远程 Web 服务通信时,具有“远程域”和“应用程序域”并在两者之间切换是非常典型的。远程域通常受到 Web 协议的限制(例如,您不能在纯 RESTful 服务中来回发送帮助方法,并且深度嵌套的对象模型很常见以避免多次 API 调用),因此不适合直接使用你的客户。

    例如:

    public static class MyFoo {
    
        private int barBazInt;
    
        public MyFoo(Foo foo) {
            this.barBazInt = parseBarBazInt();
        }
    
        public int getBarBazInt() {
            return barBazInt;
        }
    
        private int parseFooBarBazInt(Foo foo) {
            if (foo() == null) return -1;
            if (foo().getBar() == null) return -1;
            if (foo().getBar().getBaz() == null) return -1;
            return foo().getBar().getBaz().getInt();
        }
    
    }
    

    【讨论】:

      【解决方案5】:

      我编写了一个名为Snag 的类,它允许您定义在对象树中导航的路径。以下是它的使用示例:

      Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing();
      

      这意味着实例ENGINE_NAME 将有效地调用传递给它的实例上的Car?.getEngine()?.getName(),如果有任何引用返回null,则返回null

      final String name =  ENGINE_NAME.get(firstCar);
      

      它没有在 Maven 上发布,但如果有人觉得这很有用,那就是 here(当然没有保证!)

      它有点基本,但似乎可以完成这项工作。显然,对于支持安全导航或 Optional 的最新版本的 Java 和其他 JVM 语言,它已经过时了。

      【讨论】:

        【解决方案6】:

        您说某些方法“可能返回null”,但没有说明它们在什么情况下返回null。你说你抓住了NullPointerException,但你没有说你为什么抓住它。这种信息的缺乏表明您对例外的用途以及为什么它们优于替代方案没有清楚的了解。

        考虑一个用于执行操作的类方法,但该方法不能保证它将执行该操作,因为情况超出了它的控制范围(实际上是the case for all methods in Java)。我们调用该方法并返回。调用该方法的代码需要知道它是否成功。它怎么会知道?如何构建它以应对成功或失败的两种可能性?

        使用异常,我们可以编写将成功作为后置条件的方法。如果方法返回,则表示成功。如果它抛出异常,它就失败了。为了清晰起见,这是一个巨大的胜利。我们可以编写清晰地处理正常、成功案例的代码,并将所有错误处理代码移动到catch 子句中。通常情况下,方法失败的方式或原因的详细信息对调用者来说并不重要,因此相同的catch 子句可用于处理多种类型的失败。而且经常发生的情况是,一个方法根本不需要捕获异常,而可以只允许它们传播给调用者。由于程序错误导致的异常属于后者;当出现错误时,很少有方法可以做出适当的反应。

        所以,那些返回null的方法。

        • null 值是否表示您的代码中存在错误?如果是这样,您根本不应该捕获异常。而且您的代码不应该试图重新猜测自己。只要假设它会起作用,就写出清晰简洁的内容。方法调用链是否清晰简洁?然后就使用它们。
        • null 值是否表示对您的程序的输入无效?如果是这样,NullPointerException 不是一个合适的抛出异常,因为通常它是为指示错误而保留的。您可能想要抛出从IllegalArgumentException(如果您想要unchecked exception)或IOException(如果您想要检查异常)派生的自定义异常。当输入无效时,您的程序是否需要提供详细的语法错误消息?如果是这样,检查每个方法的 null 返回值然后抛出适当的诊断异常是您唯一可以做的事情。如果您的程序不需要提供详细的诊断,将方法调用链接在一起,捕获任何NullPointerException,然后抛出您的自定义异常是最清晰和最简洁的。

        其中一个答案声称链式方法调用违反了Law of Demeter,因此很糟糕。这种说法是错误的。

        • 在程序设计方面,实际上并没有关于什么是好什么是坏的绝对规则。只有启发式:大部分(甚至几乎所有)时间都是正确的规则。编程技巧的一部分是知道什么时候可以打破这些规则。因此,“这违反规则X”的简洁断言根本不是一个真正的答案。这是规则应该被打破的情况之一吗?
        • 得墨忒耳法则实际上是关于 API 或类接口设计的规则。在设计类时,有一个抽象层次很有用。您拥有使用语言原语直接执行操作并以比语言原语更高级别的抽象表示对象的低级类。您有委托给低级类的中级类,并在比低级类更高的级别上实现操作和表示。您拥有委托给中级类的高级类,并实现更高级别的操作和抽象。 (我在这里只讨论了三个抽象级别,但更多是可能的)。这使您的代码可以根据每个级别的适当抽象来表达自己,从而隐藏复杂性。 得墨忒耳法则的基本原理是,如果你有一个方法调用链,这表明你有一个高级类通过一个中级类直接处理低级细节,因此你的中级类没有提供高级类需要的中级抽象操作。但似乎不是您在这里遇到的情况:您没有设计方法调用链中的类,它们是一些自动生成的 XML 序列化代码的结果(对吗?),并且调用链没有通过抽象层次递减,因为反序列化的 XML 都在抽象层次的同一级别(对吗?)?

        【讨论】:

          【解决方案7】:

          从昨天开始一直关注这个帖子。

          我一直在评论/投票的 cmets 说,抓住 NPE 是不好的。这就是我一直这样做的原因。

          package com.todelete;
          
          public class Test {
              public static void main(String[] args) {
                  Address address = new Address();
                  address.setSomeCrap(null);
                  Person person = new Person();
                  person.setAddress(address);
                  long startTime = System.currentTimeMillis();
                  for (int i = 0; i < 1000000; i++) {
                      try {
                          System.out.println(person.getAddress().getSomeCrap().getCrap());
                      } catch (NullPointerException npe) {
          
                      }
                  }
                  long endTime = System.currentTimeMillis();
                  System.out.println((endTime - startTime) / 1000F);
                  long startTime1 = System.currentTimeMillis();
                  for (int i = 0; i < 1000000; i++) {
                      if (person != null) {
                          Address address1 = person.getAddress();
                          if (address1 != null) {
                              SomeCrap someCrap2 = address1.getSomeCrap();
                              if (someCrap2 != null) {
                                  System.out.println(someCrap2.getCrap());
                              }
                          }
                      }
                  }
                  long endTime1 = System.currentTimeMillis();
                  System.out.println((endTime1 - startTime1) / 1000F);
              }
          }
          

            public class Person {
              private Address address;
          
              public Address getAddress() {
                  return address;
              }
          
              public void setAddress(Address address) {
                  this.address = address;
              }
          }
          

          package com.todelete;
          
          public class Address {
              private SomeCrap someCrap;
          
              public SomeCrap getSomeCrap() {
                  return someCrap;
              }
          
              public void setSomeCrap(SomeCrap someCrap) {
                  this.someCrap = someCrap;
              }
          }
          

          package com.todelete;
          
          public class SomeCrap {
              private String crap;
          
              public String getCrap() {
                  return crap;
              }
          
              public void setCrap(String crap) {
                  this.crap = crap;
              }
          }
          

          输出

          3.216

          0.002

          我在这里看到了明显的赢家。进行 if 检查比捕获异常要便宜得多。我已经看到了 Java-8 的做法。考虑到 70% 的当前应用程序仍在 Java-7 上运行,我添加了这个答案。

          底线对于任何关键任务应用程序,处理 NPE 的成本都很高。

          【讨论】:

          • 100 万个请求在最坏的情况下 的额外三秒是可以测量的,但它很少会破坏交易,即使在“关键任务应用程序”中也是如此。有些系统在请求中增加 3.2 微秒是很重要的,如果你有这样的系统,一定要仔细考虑异常。但是,根据原始问题,调用 Web 服务并反序列化其输出可能需要更长的时间,并且担心异常处理的性能是无关紧要的。
          • @JeroenMostert:每张支票 3 秒/百万。所以,检查的次数会增加成本
          • 是的。尽管如此,我仍然认为这是“个人资料优先”的情况。您需要在一个请求中进行超过 300 次检查,然后该请求需要额外的一毫秒。设计考虑会比这更早地影响我的灵魂。
          • @JeroenMostert: :) 同意!我想把结果留给程序员,让他们接电话!
          【解决方案8】:

          为了提高可读性,您可能需要使用多个变量,例如

          Foo theFoo;
          Bar theBar;
          Baz theBaz;
          
          theFoo = wsObject.getFoo();
          
          if ( theFoo == null ) {
            // Exit.
          }
          
          theBar = theFoo.getBar();
          
          if ( theBar == null ) {
            // Exit.
          }
          
          theBaz = theBar.getBaz();
          
          if ( theBaz == null ) {
            // Exit.
          }
          
          return theBaz.getInt();
          

          【讨论】:

          • 在我看来,这可读性要差得多。它用一大堆与方法的实际逻辑完全无关的空检查逻辑乱扔了方法。
          【解决方案9】:

          NullPointerException 是运行时异常,所以一般来说不建议捕获它,而是避免它。

          您必须在要调用该方法的任何位置捕获异常(否则它将向上传播到堆栈)。不过,如果在您的情况下,您可以继续使用值为 -1 的结果,并且您确定它不会传播,因为您没有使用任何可能为空的“片段”,那么我认为这样做是正确的抓住它

          编辑:

          我同意@xenteros 后面的answer,最好启动你自己的异常而不是返回-1,你可以称之为InvalidXMLException

          【讨论】:

          • “不管你抓住它,它都可以传播到代码的其他部分”是什么意思?
          • 如果 null 在这句话中 wsObject.getFoo() 并且在代码的后面部分你再次运行该查询或使用 wsObject.getFoo().getBar() (例如)它会引发再次出现 NullPointerException。
          • 这是一个不寻常的措辞,用于“您必须在要调用该方法的任何地方捕获异常(否则它将传播到堆栈中)。”如果我理解正确的话。我同意这一点(这可能是个问题),我只是觉得措辞令人困惑。
          • 我会解决的,抱歉,英语不是我的母语,所以有时会发生这种情况:)谢谢
          【解决方案10】:

          假设类结构确实超出了我们的控制范围,我认为按照问题中的建议捕获 NPE 确实是一个合理的解决方案,除非性能是一个主要问题。一个小的改进可能是包装 throw/catch 逻辑以避免混乱:

          static <T> T get(Supplier<T> supplier, T defaultValue) {
              try {
                  return supplier.get();
              } catch (NullPointerException e) {
                  return defaultValue;
              }
          }
          

          现在你可以简单地做:

          return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);
          

          【讨论】:

          • return get(() -&gt; wsObject.getFoo().getBar().getBaz().getInt(), ""); 不会在编译时给出可能有问题的错误。
          【解决方案11】:

          我想添加一个着重于错误含义的答案。空异常本身并没有提供任何意义的完整错误。所以我建议避免直接与他们打交道。

          代码出错的情况有上千种:无法连接数据库、IO异常、网络错误……如果你一一处理(比如这里的空检查),那就太多了很麻烦。

          在代码中:

          wsObject.getFoo().getBar().getBaz().getInt();
          

          即使您知道哪个字段为空,您也不知道出了什么问题。也许 Bar 为空,但这是预期的吗?还是数据错误?想想读过你代码的人

          就像 xenteros 的回答一样,我建议使用自定义未检查异常。例如,在这种情况下:Foo 可以为 null(有效数据),但 Bar 和 Baz 绝不应该为 null(无效数据)

          代码可以重写:

          void myFunction()
          {
              try 
              {
                  if (wsObject.getFoo() == null)
                  {
                    throw new FooNotExistException();
                  }
          
                  return wsObject.getFoo().getBar().getBaz().getInt();
              }
              catch (Exception ex)
              {
                  log.error(ex.Message, ex); // Write log to track whatever exception happening
                  throw new OperationFailedException("The requested operation failed")
              }
          }
          
          
          void Main()
          {
              try
              {
                  myFunction();
              }
              catch(FooNotExistException)
              {
                  // Show error: "Your foo does not exist, please check"
              }
              catch(OperationFailedException)
              {
                  // Show error: "Operation failed, please contact our support"
              }
          }
          

          【讨论】:

          • 未经检查的异常表明程序员正在滥用 API。外部问题,如“无法连接数据库、IO 异常、网络错误”应通过检查异常指示。
          • 这真的取决于调用者的需要。检查异常帮助,因为它强制您处理错误。但是,在其他情况下,这不是必需的,并且可能会污染代码。例如,你的数据层有一个 IOException,你会把它扔到表示层吗?这意味着您必须捕获异常并重新抛出每个调用者。我更喜欢通过自定义 BusinessException 包装 IOException,并带有相关消息,并让它通过堆栈跟踪弹出,直到全局过滤器捕获它并将消息显示给用户。
          • 调用者不必捕获并重新抛出已检查的异常,只需声明它们被抛出即可。
          • @KevinKrumwiede:你说得对,我们只需要声明要抛出的异常。我们仍然需要声明。编辑:再看一遍,关于已检查和未检查的异常用法有很多争论(例如:programmers.stackexchange.com/questions/121328/…)。
          【解决方案12】:

          正如其他人所说,尊重德墨忒耳法则绝对是解决方案的一部分。另一部分,尽可能地改变那些链接的方法,使它们不能返回null。你可以避免返回null,而是返回一个空的String、一个空的Collection,或者其他一些代表或执行调用者对null做任何事情的虚拟对象。

          【讨论】:

            【解决方案13】:

            如果您不想重构代码并且可以使用 Java 8,则可以使用方法引用。

            先做一个简单的演示(请原谅静态内部类)

            public class JavaApplication14 
            {
                static class Baz
                {
                    private final int _int;
                    public Baz(int value){ _int = value; }
                    public int getInt(){ return _int; }
                }
                static class Bar
                {
                    private final Baz _baz;
                    public Bar(Baz baz){ _baz = baz; }
                    public Baz getBar(){ return _baz; }   
                }
                static class Foo
                {
                    private final Bar _bar;
                    public Foo(Bar bar){ _bar = bar; }
                    public Bar getBar(){ return _bar; }   
                }
                static class WSObject
                {
                    private final Foo _foo;
                    public WSObject(Foo foo){ _foo = foo; }
                    public Foo getFoo(){ return _foo; }
                }
                interface Getter<T, R>
                {
                    R get(T value);
                }
            
                static class GetterResult<R>
                {
                    public R result;
                    public int lastIndex;
                }
            
                /**
                 * @param args the command line arguments
                 */
                public static void main(String[] args) 
                {
                    WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241))));
                    WSObject wsObjectNull = new WSObject(new Foo(null));
            
                    GetterResult<Integer> intResult
                            = getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);
            
                    GetterResult<Integer> intResult2
                            = getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);
            
            
                    System.out.println(intResult.result);
                    System.out.println(intResult.lastIndex);
            
                    System.out.println();
                    System.out.println(intResult2.result);
                    System.out.println(intResult2.lastIndex);
            
                    // TODO code application logic here
                }
            
                public static <R, V1, V2, V3, V4> GetterResult<R>
                        getterChain(V1 value, Getter<V1, V2> g1, Getter<V2, V3> g2, Getter<V3, V4> g3, Getter<V4, R> g4)
                        {
                            GetterResult result = new GetterResult<>();
            
                            Object tmp = value;
            
            
                            if (tmp == null)
                                return result;
                            tmp = g1.get((V1)tmp);
                            result.lastIndex++;
            
            
                            if (tmp == null)
                                return result;
                            tmp = g2.get((V2)tmp);
                            result.lastIndex++;
            
                            if (tmp == null)
                                return result;
                            tmp = g3.get((V3)tmp);
                            result.lastIndex++;
            
                            if (tmp == null)
                                return result;
                            tmp = g4.get((V4)tmp);
                            result.lastIndex++;
            
            
                            result.result = (R)tmp;
            
                            return result;
                        }
            }
            

            输出

            241
            4


            2

            接口Getter 只是一个函数接口,你可以使用任何等价物。
            GetterResult 类,为了清楚起见剥离了访问器,保存 getter 链的结果(如果有)或最后一个 getter 被调用。

            getterChain 方法是一段简单的样板代码,可以自动生成(或在需要时手动生成)。
            我对代码进行了结构化,以便重复块是不言而喻的。


            这不是一个完美的解决方案,因为您仍然需要为每个 getter 数定义一个 getterChain 重载。

            我会改为重构代码,但如果不能重构,并且您发现自己经常使用较长的 getter 链,您可以考虑构建一个具有从 2 个到 10 个 getter 的重载的类。

            【讨论】:

              【解决方案14】:

              不要抓住NullPointerException。您不知道它来自哪里(我知道在您的情况下这不太可能,但可能其他东西扔了它)而且速度很慢。 您想访问指定的字段,为此,其他所有字段都必须不为空。这是检查每个字段的完美正当理由。我可能会在一个 if 中检查它,然后创建一种可读性方法。正如其他人指出的那样,已经返回 -1 非常老派,但我不知道您是否有理由这样做(例如与另一个系统交谈)。

              public int callService() {
                  ...
                  if(isValid(wsObject)){
                      return wsObject.getFoo().getBar().getBaz().getInt();
                  }
                  return -1;
              }
              
              
              public boolean isValid(WsObject wsObject) {
                  if(wsObject.getFoo() != null &&
                      wsObject.getFoo().getBar() != null &&
                      wsObject.getFoo().getBar().getBaz() != null) {
                      return true;
                  }
                  return false;
              }
              

              编辑:由于 WsObject 可能只是一个数据结构(检查https://stackoverflow.com/a/26021695/1528880),它是否违反得墨忒耳法则是值得商榷的。

              【讨论】:

                【解决方案15】:

                正如Tom 在评论中已经指出的那样,

                以下声明违反Law of Demeter

                wsObject.getFoo().getBar().getBaz().getInt()
                

                你想要的是int,你可以从Foo得到它。 得墨忒耳法则说,永远不要和陌生人说话。对于您的情况,您可以将实际实现隐藏在 FooBar 的幕后。

                现在,您可以在Foo 中创建方法以从Baz 获取int。最终,Foo 将拥有Bar,并且在Bar 中,我们可以访问Int,而无需将Baz 直接暴露给Foo。因此,空值检查可能被划分到不同的类中,并且类之间只共享必需的属性。

                【讨论】:

                • 它是否违反得墨忒耳法则值得商榷,因为 WsObject 可能只是一个数据结构。见这里:stackoverflow.com/a/26021695/1528880
                • @DerM 是的,这是可能的,但是由于 OP 已经有了解析他的 XML 文件的东西,他还可以考虑为所需的标签创建合适的模型类,以便解析库可以映射它们。然后这些模型类包含 null 检查其自己的子标签的逻辑。
                【解决方案16】:

                给出的答案似乎与所有其他人不同。

                我建议您在ifs 中检查NULL

                原因:

                我们不应该让我们的程序崩溃。 NullPointer 由系统生成。 系统的行为 无法预测生成的异常。你不应该离开你的 程序在系统手中的时候你已经有了处理方式 它由你自己。并加入异常处理机制以增加安全性。!!

                为了让你的代码易于阅读,试试这个检查条件:

                if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null) 
                   return -1;
                else 
                   return wsObject.getFoo().getBar().getBaz().getInt();
                

                编辑:

                这里需要存储这些值wsObject.getFoo(), wsObject.getFoo().getBar(), wsObject.getFoo().getBar().getBaz() 在 一些变量。我不这样做是因为我不知道回报 该函数的类型。

                任何建议将不胜感激..!!

                【讨论】:

                • 您是否认为 getFoo() 是一个非常耗时的操作?您应该将返回值存储在变量中,但这会浪费内存。您的方法非常适合 C 编程。
                • 但有时最好延迟 1 毫秒然后程序崩溃@xenteros..!!
                • getFoo() 可能从位于另一个大陆的服务器获取值。它可以持续任何时间:分钟/小时...
                • wsObject 将包含从 Webservice 返回的值..!!该服务将被调用,wsObject 将获得一个长的XML 数据作为网络服务响应..!!所以没有什么像 server 位于另一个大陆 因为 getFoo() 只是一个获取 getter 方法 的元素,而不是 Webservice 调用..!! @xenteros
                • 好吧,从 getter 的名称来看,我假设它们返回 Foo、Bar 和 Baz 对象:P 还考虑从您的答案中删除提到的双重安全性。除了代码的污染之外,我认为它没有提供任何真正的价值。通过健全的局部变量和空值检查,我们已经做了足够多的工作来确保代码的正确性。如果可能发生异常,则应将其视为一个。
                【解决方案17】:

                值得考虑创建自己的异常。我们称之为 MyOperationFailedException。你可以抛出它而不是返回一个值。结果将是相同的——您将退出该函数,但您不会返回硬编码值 -1,它是 Java 反模式。在 Java 中,我们使用异常。

                try {
                    return wsObject.getFoo().getBar().getBaz().getInt();
                } catch (NullPointerException ignored) {
                    throw new MyOperationFailedException();
                }
                

                编辑:

                根据 cmets 中的讨论,让我补充一下我之前的想法。在这段代码中有两种可能性。一种是你接受 null,另一种是它是一个错误。

                如果发生错误,您可以在断点不足时使用其他结构调试代码。

                如果可以接受,您就不会关心这个 null 出现在哪里。如果你这样做了,你绝对不应该链接这些请求。

                【讨论】:

                • 你不认为抑制异常是个坏主意吗?实时地,如果我们失去了异常的踪迹,那么真正痛苦的是要找出到底发生了什么!我总是建议不要使用链接。我看到的第二个问题是:这段代码不能在某个时间点被授权,哪个结果为空。
                • 不,你的异常可以有一条消息,它肯定会指出它被抛出的地方。我同意链接不是最好的解决方案:)
                • 不,它只会说行号。因此,链中的任何调用都可能导致异常。
                • “如果发生错误,你可以调试你的代码”——不在生产环境中。当我只有一个日志时,我宁愿知道什么失败了,而不是试图预测导致它失败的原因。有了这个建议(和那个代码),你真正知道的是 4 件事之一是空的,但不是哪一个或为什么。
                【解决方案18】:

                我的回答与@janki 几乎在同一行,但我想稍微修改一下代码 sn-p 如下:

                if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) 
                   return wsObject.getFoo().getBar().getBaz().getInt();
                else
                   return something or throw exception;
                

                您也可以为wsObject 添加一个空检查,如果该对象有可能为空。

                【讨论】:

                  【解决方案19】:

                  如果效率是一个问题,那么应该考虑“catch”选项。 如果 'catch' 不能使用,因为它会传播(如 'SCouto' 所述),则使用局部变量来避免多次调用方法 getFoo()getBar()getBaz()

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2020-02-28
                    • 2016-03-09
                    • 1970-01-01
                    • 2016-02-08
                    • 2021-06-13
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多