【问题标题】:Is initializing a variable to null an antipattern?将变量初始化为空是反模式吗?
【发布时间】:2021-03-03 16:49:27
【问题描述】:

总结

我正在看一个场景,例如:

File someFile = null;
try
{
   someFile = File.createTempFile( SOME_PREFIX, SOME_SUFFIX, targetDirectory );
}
catch( IOException e )
{
    throw new SomeException( "Unable to create file for domain specific task", e, SomeExceptionErrorCode.FILE_MANAGEMENT_ERROR );
}
            
try( BufferedOutputStream stream = new BufferedOutputStream( new FileOutputStream( someFile.getAbsolutePath() ) ) )
{
    stream.write( byteData, 0, byteData.length );
    stream.flush();
}
catch( IOException e )
{
    throw new SomeException( "Unable to write domain specific data to domain specific file", e, SomeExceptionErrorCode.FILE_MANAGEMENT_ERROR );
}

对于这种情况,someFile 被初始化为 null。我的目的是将这段代码翻译成遵循正确做法的东西。

我的考虑

  • 只需将someFile初始化为null即可,如当前代码sn-p所示。但是通常我会避免这种情况,所以到目前为止这似乎并不令人满意
  • 用一个空的String例如初始化someFile。这提供了File 的默认实例。我看到的问题是,如果这个错误处理在未来发生变化,一个带有废话属性的有效File 可能会被传递到代码中的其他地方。
  • 嵌套try-catch 块。这确实有效,但是出于某种原因感觉很糟糕,尤其是因为两个嵌套块都捕获了IOException
  • 还考虑了一个Optional<File>,但是我不相信如果每个try-catch块初始化了一个有点复杂的对象以在该块之外使用,是否证明使用Optional是合理的

问题

someFile 初始化为null 是一种反模式吗?如果是这样,如何处理最好的场景,例如发布的场景?

【问题讨论】:

  • 您可以简单地省略= null 部分。 File someFile; 在此上下文中有效。
  • 两个答案都非常适合我的情况,谢谢。请注意,从技术上讲,这个问题仍然没有答案。虽然这些答案确实帮助我改进了我的代码,但关于使用null 初始化变量是否是不好的做法这一普遍问题尚未得到解答。
  • 最初我想在没有任何上下文的情况下放置这个问题,但是没有上下文的单行词往往会引起负面关注,这就是为什么我提供了我想要解决的明确场景。
  • 使用null 初始化通常不是坏习惯。它是语言的基本部分。另一种方法是:将 everything 包装在 Optionals 中。我会说 this 是一种不好的做法,因为它会使代码更加难以阅读。这就是代码中最重要的部分:它应该很容易理解! ... 另一件要提的事情:使用 null 进行初始化然后覆盖它会告诉编译器这个变量不是“实际上是最终的”。虽然它不能在 lambda 表达式中使用。也许(只是猜测)它也会阻止编译器优化。

标签: java exception try-catch


【解决方案1】:

这样的事情怎么样:

public void yourMethod() {
  File file = createFile();
  writeFile(file);
}

private File createFile() {
  try {
    return File.createTempFile(...);
  } catch(...) {
    ...
  }
}

private void writeFile(File file) {
  try(...) {
    ...
  } catch(...) {
    ...
  }
}

因此您的方法保持简洁且易于理解。

编辑:甚至从createFile返回Optional<File>

private Optional<File> createFile() {
  try {
    return Optional.of(File.createTempFile(...));
  } catch(...) {
    ...
    return Optional.empty();
  }
}

那么你可以在yourMethod中使用Optional.ifPresent

public void yourMethod() {
  Optional<File> file = createFile();
  file.ifPresent(value -> writeFile(value));

  // or shorter:
  createFile()
    .ifPresent(this::writeFile);

  // depends on how exactly the methods receive their parameters
}

【讨论】:

  • 正如开篇文章中所写,我考虑过使用Optional,但是我想知道这是否会在一项相当微不足道的任务中引入不必要的复杂性。但是,我确实喜欢在使用子例程时通过将方法引用传递给 Optional#ifPresent 所引入的简洁性。
  • 由于 Java 8 基本上我所有的“可能为 null”的方法都返回 Optional,所以我的代码中没有任何方法返回 null。每个方法要么总是返回一些Object(那么就不需要Optional)或者它返回一个Optional。这样就不会再出现由方法返回值引起的意外 NullPointerException。但是当涉及到局部变量(在一个方法中)时,我基本上从不创建 Optionals,因为它使事情变得复杂:在一个 10 行长的方法中,我可以很容易地看到发生了什么。但是当调用一个方法时,我不想总是查看它的实现。
  • 我可以确认这一点,但是通过将Optional 猛烈抨击到我碰巧遇到的每一个微不足道的场景中,请注意不要过度使用Optional。最后,我检查了Optional 在其声明的设计目标范围之外的使用情况,这是有争议的,但也许事情已经发生了变化。就我个人而言,我更喜欢使用 Optional 甚至作为成员而不是 null 返回/分配。
  • 关于Optional辩论的过度使用,我指的是这里的一些回复和cmets,特别是链接材料:stackoverflow.com/questions/23454952/uses-for-optional
  • @Koenigsberg 规则很简单: (A) 如果 null 是从方法返回的合法值,请使用 Optional。这不是过度使用,这正是创建 Optional 的原因(参见 Brian Goetz 的著作)。 (B) 如果 null 不是从方法返回的合法值,则如果即将返回 null,则该方法应调用 Objects.requireNonNull 以引发异常。 (C) 在任何一种情况下,calling 方法都不应该负责 null 检查,calling 方法负责 null 检查(或包装在 Optional 中)。好吧,这就是你控制代码的规则。
【解决方案2】:

你可以拥有

File someFile;

没有任何显式赋值。

Java 通常会在该变量有值之前抱怨使用该变量,但编译器足够聪明,可以理解该变量可能没有值的唯一方法是 createTempFile 抛出 IOException,但既然你抓住它,然后再次throw 它知道该方法在这里退出或someFile 具有正确的值。因此,someFile.getAbsolutePath() 的后续用法是允许的。

这比null 更干净,因为现在如果你例如删除重新抛出的异常,您的代码将不再编译,因为现在编译器无法再推断将始终分配的值。如果您使用 null 初始化并删除重新抛出,您稍后将遇到 NPE。

这里不需要选项,因为在这种情况下编译器可以区分非初始化值和初始化值。

【讨论】:

  • 这是一些很好的见解,谢谢。我知道能够在没有任何赋值的情况下声明变量,但不知道编译器能够对这种情况进行上下文化并识别其有效性。
猜你喜欢
  • 2015-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-01
  • 2011-02-15
相关资源
最近更新 更多