【问题标题】:Should a retrieval method return 'null' or throw an exception when it can't produce the return value? [closed]检索方法是否应该在无法产生返回值时返回“null”或抛出异常? [关闭]
【发布时间】:2010-09-15 14:07:01
【问题描述】:

我正在使用java语言,我有一个方法,如果找到它应该返回一个对象。

如果没有找到,我应该:

  1. 返回空值
  2. 抛出异常
  3. 其他

最佳实践或惯用语是什么?

【问题讨论】:

  • 无论你做什么,一定要记录下来。我认为这一点比究竟哪种方法“最好”更重要。
  • 这取决于编程语言的流行习语。请用编程语言标记这个问题。
  • 返回 null 可能只意味着成功或失败,这通常不是很多信息(某些方法可能会在很多方面失败)。库应该更好地抛出异常以明确错误,这样主程序可以决定如何在更高级别处理错误(与内置错误处理逻辑相反)。
  • 在我看来,真正被问到的问题是我们是否应该认为找不到实体是例外的,如果是,为什么?没有人真正充分回答如何得出这个结论,现在问答已经结束。业界没有就这个重要话题达成共识,真是令人遗憾。是的,我知道它取决于。所以,解释为什么它不仅仅依赖于“如果异常,抛出”

标签: java exception error-handling null


【解决方案1】:

这取决于您的语言和代码是否促进: LBYL(在你跳跃之前看看) 要么 EAFP(请求宽恕比请求许可更容易)

LBYL 说你应该检查值(所以返回 null)
EAFP 说只是尝试操作,看看它是否失败(抛出异常)

虽然我同意上述观点.. 异常应该用于异常/错误条件,并且在使用检查时最好返回 null。


EAFP 与 Python 中的 LBYL:
http://mail.python.org/pipermail/python-list/2003-May/205182.html (Web Archive)

【讨论】:

  • 在某些情况下,EAFP 是唯一有意义的方法。例如,在并发映射/字典中,无法在请求映射时询问映射是否存在。
【解决方案2】:

如果您正在使用引发异常的库或其他类,则应重新引发它。这是一个例子。 Example2.java 就像库,而 Example.java 使用它的对象。 Main.java 是处理此异常的示例。您应该向调用方的用户显示有意义的消息和(如果需要)堆栈跟踪。

Main.java

public class Main {
public static void main(String[] args) {
    Example example = new Example();

    try {
        Example2 obj = example.doExample();

        if(obj == null){
            System.out.println("Hey object is null!");
        }
    } catch (Exception e) {
        System.out.println("Congratulations, you caught the exception!");
        System.out.println("Here is stack trace:");
        e.printStackTrace();
    }
}
}

Example.java

/**
 * Example.java
 * @author Seval
 * @date 10/22/2014
 */
public class Example {
    /**
     * Returns Example2 object
     * If there is no Example2 object, throws exception
     * 
     * @return obj Example2
     * @throws Exception
     */
    public Example2 doExample() throws Exception {
        try {
            // Get the object
            Example2 obj = new Example2();

            return obj;

        } catch (Exception e) {
            // Log the exception and rethrow
            // Log.logException(e);
            throw e;
        }

    }
}

Example2.java

 /**
 * Example2.java
 * @author Seval
 *
 */
public class Example2 {
    /**
     * Constructor of Example2
     * @throws Exception
     */
    public Example2() throws Exception{
        throw new Exception("Please set the \"obj\"");
    }

}

【讨论】:

  • 如果要从函数中抛出的异常不是运行时异常,并且调用者应该处理它(而不是仅仅终止程序),那么不要从内部子系统中抛出异常调用者不能期望,最好用外部检查异常包装内部异常,同时“链接”内部异常,以便调试人员可以找出引发外部异常的原因。例如,example1 可以使用 'throw new MyCheckedException("Please set the \"obj\"", e)' 将 'e' 包含在抛出的异常中。
【解决方案3】:

如果该方法返回一个集合,则返回一个空集合(如上所述)。但请不要 Collections.EMPTY_LIST 之类的! (在 Java 的情况下)

如果该方法检索单个对象,那么您有一些选择。

  1. 如果方法应该总是找到结果并且找不到对象是真正的异常情况,那么您应该抛出异常(在 Java 中:请提供未经检查的异常)
  2. (仅限 Java)如果您可以容忍该方法抛出已检查异常,则抛出项目特定的 ObjectNotFoundException 等。在这种情况下,如果您忘记处理异常,编译器会告诉您。 (这是我对 Java 中未找到的东西的首选处理方式。)
  3. 如果你说真的没问题,如果没有找到对象,而且你的方法名是findBookForAuthorOrReturnNull(..),那么你可以返回null。在这种情况下,强烈建议使用某种静态检查或编译器检查,以防止在没有空检查的情况下取消引用结果。在Java的情况下,它可以是例如。 FindBugs(参见 http://findbugs.sourceforge.net/manual/annotations.html 的 DefaultAnnotation)或 IntelliJ-Checking。

如果您决定返回空值,请小心。如果您不是项目中唯一的程序员,您将在运行时获得 NullPointerExceptions(在 Java 或其他语言中)!所以不要返回编译时未检查的空值。

【讨论】:

  • 如果代码被正确编写以期望null,则不会。有关更多信息,请参阅投票最多的答案。
  • 但前提是您确保在编译时检查所有空值。这可以通过在包级别使用 FindBugs @NutNull 并将您的方法标记为“可能返回 null”来完成。或者使用像 Kotlin 或 Nice 这样的语言。但是不返回 null 要简单得多。
  • “更简单”,也许。但通常是完全错误的
  • 再次:阅读投票最多的答案以获取更多信息。基本上:如果无法找到所请求的书是一个潜在的预期结果,那么当根本找不到所请求的书时,一个例外是错误的事情,而不是发生一些错误。
  • 你在这里误解了很多。你普遍应用有条件的建议,这几乎总是一件坏事。阅读其余的投票答案。您的回答只是陈述了一个绝对性,并为此提出了极其错误的逻辑。
【解决方案4】:

抛出异常的好处:

  1. 调用代码中的更清晰的控制流。检查 null 会注入一个条件分支,该分支由 try/catch 本地处理。检查 null 并不表示您正在检查的是什么 - 您检查 null 是因为您正在寻找您期望的错误,还是检查 null 以便您不会在下行链上进一步传递它?
  2. 消除“null”含义的歧义。 null 代表错误还是 null 是实际存储在值中的内容?很难说当你只有一件事可以做出决定时。
  3. 提高了应用程序中方法行为之间的一致性。 异常通常暴露在方法签名中,因此您可以更清楚地了解应用程序中的方法处理哪些边缘情况,以及您的应用程序可以对哪些信息做出反应以可预测的方式。

更多示例说明请见:http://metatations.com/2011/11/17/returning-null-vs-throwing-an-exception/

【讨论】:

  • +1 因为第 2 点非常好 - null 与未找到的含义不同。这在处理动态语言时变得更加重要,其中 Null 实际上可能是函数存储/检索的对象 - 在这种情况下
  • 第 2 点 +1。 null 是错误吗?为给定键存储的内容是 null 吗? null 是否表示密钥不存在?这些是真正的问题,清楚地表明返回 null 几乎永远不会正确。这可能是这里最好的答案,因为其他人刚刚抛出了模棱两可的“如果它是例外的,那就扔”
  • 与返回相比,异常可能会导致性能损失(例如在 Java 中)。未经检查的异常(例如在 Java 中)可能会出现在意想不到的地方。
【解决方案5】:

不幸的是 JDK 不一致,如果您尝试访问资源包中不存在的键,则会出现未找到异常,并且当您从 map 请求值时,如果它不存在,则会得到 null。因此,我会将获胜者的答案更改为以下内容,如果找到的值可以为 null,则在未找到时引发异常,否则返回 null。所以遵循规则,有一个例外,如果你需要知道为什么找不到值,那么总是引发异常,或者..

【讨论】:

    【解决方案6】:

    宁愿返回 null --

    如果调用者在没有检查的情况下使用它,异常就会在那里发生。

    如果调用者没有真正使用它,请不要向他征税try/catch

    【讨论】:

      【解决方案7】:

      仅指 null 不被认为是异常行为的情况,我绝对是 try 方法,很明显,不需要像这里所说的“阅读本书”或“跳之前看看”

      基本上是这样:

      bool TryFindObject(RequestParam request, out ResponseParam response)
      

      这意味着用户的代码也将是清晰的

      ...
      if(TryFindObject(request, out response)
      {
        handleSuccess(response)
      }
      else
      {
        handleFailure()
      }
      ...
      

      【讨论】:

        【解决方案8】:

        我只是想概括一下前面提到的选项,并添加一些新选项:

        1. 返回空值
        2. 抛出异常
        3. 使用空对象模式
        4. 为您的方法提供一个布尔参数,以便调用者可以选择是否希望您抛出异常
        5. 提供一个额外的参数,这样调用者可以设置一个值,如果没有找到值,他会返回该值

        或者你可以结合这些选项:

        提供几个重载版本的 getter,以便调用者可以决定走哪条路。在大多数情况下,只有第一个实现了搜索算法,其他的只是围绕第一个:

        Object findObjectOrNull(String key);
        Object findObjectOrThrow(String key) throws SomeException;
        Object findObjectOrCreate(String key, SomeClass dataNeededToCreateNewObject);
        Object findObjectOrDefault(String key, Object defaultReturnValue);
        

        即使您选择只提供一种实现,您也可能希望使用这样的命名约定来阐明您的合同,如果您决定添加其他实现,它也会对您有所帮助。

        您不应该过度使用它,但它可能会有所帮助,尤其是在编写一个帮助类时,您将在数百个具有许多不同错误处理约定的不同应用程序中使用它。

        【讨论】:

        • 我喜欢清晰的函数名,尤其是 orCreate 和 orDefault。
        • 大部分代码都可以用Expected<T> findObject(String) 更简洁地编写,其中Expected<T> 具有orNull()orThrow()orSupplied(Supplier<T> supplier)orDefault(T default) 的功能。这从错误处理中抽象了数据的获取
        • 直到现在我才知道 Expected。我似乎很新,当我写原始答案时可能不存在。也许你应该让你的评论成为一个正确的答案。
        • 另外,Expected 是一个 C++ 模板。是否也有其他面向对象语言的实现?
        • 在 Java 8 中,返回 Optional(在其他语言中称为 Maybe 等)也是一种选择。这清楚地向调用者表明有可能不返回任何内容,并且如果调用者没有处理这种可能性,则不会编译,而不是 null,即使调用者不检查它(无论如何在 Java 中)也会编译.
        【解决方案9】:

        如果 null 从不表示错误,则返回 null。

        如果 null 始终是错误,则抛出异常。

        如果 null 有时是异常,则编写两个例程。一个例程抛出异常,另一个是布尔测试例程,它在输出参数中返回对象,如果找不到对象,则例程返回 false。

        很难误用 Try 例程。忘记检查 null 真的很容易。

        所以当 null 是一个错误时你就写

        object o = FindObject();
        

        当 null 不是错误时,您可以编写类似的代码

        if (TryFindObject(out object o)
          // Do something with o
        else
          // o was not found
        

        【讨论】:

        • 如果 C# 提供真正的元组,这将是一个更有用的建议,因此我们可以避免使用 [out] 参数。不过,这是首选模式,所以 +1。
        • 在我看来,try 方法是最好的方法。您不必查找如果无法返回对象会发生什么。使用 Try 方法,您立即知道该怎么做。
        • 提醒我来自 Laravel Eloquent 的 findfindOrFail
        • @ErikForbes 我知道您的评论已经过时了,但答案难道不是定义一个从TryFindObject 方法返回的多属性对象吗?对于不想花时间定义封装多个值的对象的程序员来说,元组似乎更像是一种懒惰的范式。无论如何,这基本上所有元组都是核心。
        • @crush - 命名元组文字是一个选项,IMO。这是指向带有元组的异步 Try Get 模式的链接。 stackoverflow.com/questions/1626597/…
        【解决方案10】:

        这里还有一些建议。

        如果返回一个集合,避免返回null,返回一个空集合,这使得枚举更容易处理,而无需先进行null检查。

        几个 .NET API 使用 throwedOnError 参数的模式,如果找不到对象,调用者可以选择是否真的是异常情况。 Type.GetType 就是一个例子。 BCL 的另一个常见模式是 TryGet 模式,其中返回布尔值并通过输出参数传递值。

        您还可以在某些情况下考虑 Null Object 模式,它可以是默认值,也可以是没有行为的版本。关键是避免在整个代码库中进行空检查。请参阅此处了解更多信息http://geekswithblogs.net/dsellers/archive/2006/09/08/90656.aspx

        【讨论】:

          【解决方案11】:

          例外情况与按合同设计有关。

          对象的接口实际上是两个对象之间的契约,调用者必须满足契约,否则接收者可能会失败并抛出异常。有两种可能的合同

          1) 所有输入的方法都是有效的,这种情况下找不到对象时必须返回null。

          2) 只有某些输入是有效的,即导致找到对象的输入。在这种情况下,您必须提供第二种方法,允许调用者确定其输入是否正确。例如

          is_present(key)
          find(key) throws Exception
          

          如果并且仅当您提供第二个合同的两种方法时,您才可以抛出异常,即什么都没有找到!

          【讨论】:

            【解决方案12】:

            返回 null 而不是抛出异常,并在 API 文档中清楚地记录 null 返回值的可能性。如果调用代码不遵守 API 并检查 null 情况,它很可能无论如何都会导致某种“空指针异常”:)

            在 C++ 中,我可以想到 3 种不同风格的设置查找对象的方法。

            选项 A

            Object *findObject(Key &key);
            

            找不到对象时返回 null。很好很简单。我会和这个一起去。以下替代方法适用于不讨厌超参数的人。

            选项 B

            void findObject(Key &key, Object &found);
            

            传入对将接收对象的变量的引用。当找不到对象时,该方法抛出异常。如果不是真的期望找不到对象,则此约定可能更合适 - 因此您抛出异常以表示这是一个意外情况。

            选项 C

            bool findObject(Key &key, Object &found);
            

            当找不到对象时,该方法返回 false。与选项 A 相比,此选项的优点是您可以在一个清晰的步骤中检查错误情况:

            if (!findObject(myKey, myObj)) { ...
            

            【讨论】:

              【解决方案13】:

              例外应该是例外。返回 null 如果有效则返回 null

              【讨论】:

              【解决方案14】:

              问问自己:“找不到对象是例外情况吗”?如果预计它会在您的程序的正常过程中发生,您可能不应该引发异常(因为它不是异常行为)。

              短版:使用异常来处理异常行为,而不是处理程序中的正常控制流。

              -艾伦。

              【讨论】:

                【解决方案15】:

                使用空对象模式或抛出异常。

                【讨论】:

                • 这是真正的答案。返回 null 是懒惰的程序员的一个坏习惯。
                • 我不敢相信这个答案还没有被投票给顶部。这是真正的答案,任何一种方法都非常简单,并且可以节省大量代码膨胀或 NPE。
                • 如果使用空对象模式,如何区分键映射到空对象的情况和键没有映射的情况?我认为返回一个无意义的对象比返回 null 要糟糕得多。将 null 返回给不准备处理它的代码通常会导致抛出异常。不是例外的最佳选择,但仍然是例外。返回无意义的对象更有可能导致代码错误地将无意义的数据视为正确。
                • 空对象在实体查找中的表现如何?例如,Person somePerson = personRepository.find("does-not-exist"); 假设此方法返回 ID does-not-exist 的空对象。那么somePerson.getAge() 的正确行为是什么?现在,我还不相信空对象模式是实体查找的正确解决方案。
                【解决方案16】:

                正常操作时可能会出现不包含对象的情况,应由调用者返回NULL处理。

                如果不包含对象表明调用代码或内部状态存在错误,则执行断言。

                如果不包含该对象,则表示偶发事件。 (就像有人在您同时签出商品时从商店中删除了商品一样。)然后抛出异常。

                【讨论】:

                  【解决方案17】:

                  在某些函数中我添加了一个参数:

                  ..., bool verify = true)
                  

                  True 表示抛出,false 表示返回一些错误返回值。这样,无论谁使用此功能,都有两种选择。默认值应该是 true,以便那些忘记错误处理的人受益。

                  【讨论】:

                    【解决方案18】:

                    返回一个空值,异常就是这样:你的代码做的事情不是预期的。

                    【讨论】:

                      【解决方案19】:

                      或者返回一个选项

                      一个选项基本上是一个容器类,它强制客户处理展位案例。 Scala有这个概念,查一下它的API。

                      然后,您在此对象上有 T getOrElse(T valueIfNull) 之类的方法,它们要么返回找到的对象,要么返回客户端指定的替代方案。

                      【讨论】:

                        【解决方案20】:

                        在数据层代码中,我有时会使用下面的代码,让调用者判断“object not found”是否意味着发生了错误。

                        
                        DataType GetObject(DBConnection conn, string id, bool throwOnNotFound) {
                            DataType retval = ... // find object in database
                            if (retval != null || ! throwOnNotFound) {
                                return retval;
                            } else {
                                throw new NoRowsFoundException("DataType object with id {id} not found in database");
                            }
                        }
                        
                        DataType GetObject(DBConnection conn, string id) {
                            return GetObject(conn, id, true);
                        } 
                        

                        【讨论】:

                          【解决方案21】:

                          我同意这里似乎达成的共识(如果“未找到”是正常的可能结果,则返回 null,或者如果情况的语义要求始终找到对象,则抛出异常)。

                          但是,根据您的具体情况,第三种可能性可能是有意义的。您的方法可以在“未找到”条件下返回某种默认对象,从而确保调用代码始终接收有效对象,而无需进行空值检查或异常捕获。

                          【讨论】:

                            【解决方案22】:

                            不要认为有人提到异常处理中的开销 - 需要额外的资源来加载和处理异常,所以除非它是真正的应用程序终止或进程停止事件(继续前进会弊大于利)我会选择传回调用环境可以按照它认为合适的方式解释的值。

                            【讨论】:

                              【解决方案23】:

                              “其他”选项可以是让 find 方法接受一个带有默认对象的附加参数,如果找不到所寻找的对象,则返回该参数。

                              否则我只会返回 null 除非确实是未找到对象的例外情况。

                              【讨论】:

                                【解决方案24】:

                                与您使用的 API 保持一致。

                                【讨论】:

                                  【解决方案25】:

                                  这取决于你的方法。如果您的方法应该总是返回一个有效的对象并且没有找到,那么抛出异常是要走的路。如果该方法只是为了返回一个可能存在或不存在的对象(例如可能是联系人的图像),则不应引发错误。

                                  如果此方法实际上将返回一个对象,您可能还希望公开一个返回布尔值 true/false 的方法,这样您就不必 a) 检查 null 或 b) 捕获异常

                                  【讨论】:

                                    【解决方案26】:

                                    如果没有发现它是异常事件(即在正常情况下应该存在),则抛出。否则,返回一个“未找到”值(可以为 null,但不必如此),甚至让该方法返回一个 boolean 表示 found/notfound 和一个 out 参数表示实际对象。

                                    【讨论】:

                                      【解决方案27】:

                                      只要它应该返回一个对象的引用,返回一个NULL应该是好的。

                                      但是,如果它返回整个该死的东西(就像在 C++ 中,如果你这样做:'return blah;' 而不是 'return &blah;'(或者 'blah' 是一个指针),那么你不能返回一个 NULL ,因为它不是“对象”类型。在这种情况下,抛出异常或返回未设置成功标志的空白对象是我解决问题的方法。

                                      【讨论】:

                                        【解决方案28】:

                                        这取决于方法的性质及其使用方式。如果可能找不到对象是正常行为,则返回 null。如果总是找到对象是正常行为,则抛出异常。

                                        根据经验,仅当出现异常情况时才使用异常。不要以这样一种方式编写代码,即异常抛出和捕获是其正常操作的一部分。

                                        【讨论】:

                                          【解决方案29】:

                                          取决于找不到对象的含义。

                                          如果是正常状态,则返回null。这只是偶尔会发生的事情,调用者应该检查它。

                                          如果是错误,则抛出异常,调用者应决定如何处理丢失对象的错误情况。

                                          最终两者都可以,尽管大多数人通常认为仅在发生异常情况时才使用异常是一种好习惯。

                                          【讨论】:

                                          • 您将如何详细说明“正常情况”语句以及您将使用什么标准来区分它与错误。
                                          • @user1451111 我在软件工程上找到了关于what the normal state of affairs is的相关讨论
                                          【解决方案30】:

                                          作为一般规则,如果该方法应该始终返回一个对象,那么就使用例外。如果您预计偶尔会出现 null 并希望以某种方式处理它,请使用 null。

                                          无论你做什么,我强烈建议不要使用第三种选择:返回一个显示“WTF”的字符串。

                                          【讨论】:

                                          • 加一个,因为在过去的美好时光里,我做了几次以上作为快速肮脏的“临时”修复......不是好主意。特别是如果你是学生,它会被审查。
                                          • 我打算投反对票,因为 WTF 选项对我来说似乎很棒......但显然我有一颗心
                                          • 抛出新的 WtfExcepti?n
                                          • 我认为,如果这个答案讨论了方法总是返回对象而不是“偶尔为空”的原因,那将会很有帮助。是什么保证了这种情况?请举例说明何时可能存在这种情况。
                                          猜你喜欢
                                          • 1970-01-01
                                          • 2011-04-27
                                          • 2012-12-22
                                          • 2012-06-08
                                          • 1970-01-01
                                          • 1970-01-01
                                          • 2017-12-11
                                          • 1970-01-01
                                          • 1970-01-01
                                          相关资源
                                          最近更新 更多