【问题标题】:Try-Catch Instead of Null Check When Using Several Getters使用多个 Getter 时使用 Try-Catch 代替 Null 检查
【发布时间】:2016-12-26 12:56:44
【问题描述】:

我的问题如下,我有一个相当长的Getter,即,

objectA.getObjectB().getObjectC().getObjectD().getObjectE().getName();

由于“糟糕”的数据库/实体设计(有些东西比其他东西引入得晚),getObjectB()getObjectC()getObjectD() 可能会返回 NULL

通常我们一直使用空检查,但在这种情况下,我必须使用

ObjectB b = objectA.getObjectB();
if (b != null) {
    ObjectC c = b.getObjectC();
    if (c != null) {
        ObjectD d = c.getObjectD();
        if (d != null)
           return d.getObjectE().getName();
    }
}
return "";

相反,简单地使用 try-catch 块会更容易

try {
   return objectA.getObjectB().getObjectC().getObjectD().getObjectE().getName();
} catch (NullPointerException e) {
   return "";
}

在这种情况下,我并不关心哪个对象返回 NULL,它要么显示名称,要么不显示。使用 try-catch 代替检查是否有任何复杂性或者是糟糕的设计?

感谢您的意见。

【问题讨论】:

  • 如果 getter 返回 null 不是错误,则将异常用于完全正确的情况通常是一个糟糕的设计。
  • 多个 if/else 会增加圈复杂度!这是肯定的!
  • 如果你的项目看起来很垃圾,并且你不会重新做项目架构,试着写一些性能测试,看看 null check 和 try-catch 之间的区别。
  • 另外,如果你要写性能测试,请在这里写下你的结果。
  • 这就是为什么 java 需要一个空证明点运算符...object?.getObjectB()?.getObjectC()...

标签: java


【解决方案1】:

如果是使用 Java 8 的选项,您可以使用Optional,如下所示:

Optional.ofNullable(objectA)
    .map(a -> a.getObjectB())
    .map(b -> b.getObjectC())
    .map(c -> c.getObjectD())
    .map(d -> d.getObjectE())
    .map(e -> e.getName())
    .orElse("");

【讨论】:

  • @ST-DDT 这很好用,我已经在生产代码中使用了它——可选的 map() 方法不会执行映射器函数,除非可选包含一个值。
  • 我将 Optionals 与 Streams 混淆了。
  • @adrian 如果 first 对象为空,但我认为其他任何对象都不是,则此方法有效。如果其中一个返回 null,你就不会得到空的可选项,对吧?
  • @Joshua Taylor 如果函数返回null,那么Optional.map返回Optional.empty()
  • @Hoopje 是的,当然,没错;如果没有,Optional#map 将几乎没有用处。这就是我在喝第一杯咖啡之前发表评论的结果。 :\
【解决方案2】:

这种方法链接被称为“火车残骸”,不是首选。 这样的声明也违反了Law of Demeter。让我给你一个例子,来自Robert C MartinClean code一书:

String scratchDirPath = ctxt.getOptions().getScratchDir().getAbsolutePath();
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(scratchDirPath));
//write to file...

这与您所拥有的相似,这是一种不好的做法。这至少可以重构为:

Options options = ctxt.getOptions();
File scratchDir = options.getScratchDir();
String scratchDirPath = scratchDir.getAbsolutePath();
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(scratchDirPath));
//write to file...

这仍然违反了得墨忒耳法则,但至少部分好一些。 最可取的方法是找出为什么需要 scratchDirPath 并要求 ctxt 对象将其提供给您。所以它看起来像 -

BufferedOutputStream bos = ctxt.createScratchDirFileStream();

这样 ctxt 不会暴露其所有内部结构。这将调用代码与 ctxt 的实现分离。

【讨论】:

    【解决方案3】:

    这确实是一个只有您和您的团队才能做出的判断,但(至少)我要指出一件客观的事情:如果其中一个方法返回 null,您的第一个代码块将执行比你的第二个更好。相对于简单的分支,抛出异常是昂贵的。 (在没有任何方法返回 null 的情况下,我认为第二个可能几乎比第二个更快,因为您避免了分支。)在任何一种情况下,如果性能很重要,请测试它在你的环境中。 (但是test it properly;微基准测试很难。使用工具。)

    当然,在大多数情况下,这种差异在现实世界中并不重要,但这是运行时的显着差异。

    【讨论】:

      猜你喜欢
      • 2019-12-04
      • 2012-01-28
      • 1970-01-01
      • 1970-01-01
      • 2012-10-24
      • 2020-04-02
      • 2012-04-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多