如果检索方法无法产生返回值,它应该返回“ null”还是抛出异常?[关闭]


503

我有一种方法,应该在找到对象后返回它。

如果找不到,我应该:

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

57
无论做什么,请确保将其记录在案。我认为这一点比确切地说哪种“最佳”方法更为重要。
瑞克

6
这取决于编程语言的流行习惯用法。请用编程语言标签标记该问题。
Teddy

3
返回null可能仅意味着成功或失败,而成功或失败通常不是很多信息(某些方法可能以多种方式失败)。库应该更好地抛出异常以使错误明确化,这样主程序可以决定如何在更高级别上处理错误(与内置错误处理逻辑相反)。
3k-

1
在我看来,真正要问的问题是,我们是否应认为找不到实体是例外情况,如果是,为什么?没有人真正回答如何得出这个结论,现在问答已经结束。业界还没有就这个重要话题达成共识,真是遗憾。是的,我知道这取决于。因此,请解释为什么它不仅仅取决于“如果异常,抛出”
粉碎

Answers:


484

如果您一直期望找到一个值,那么如果缺少该值,则抛出异常。异常将意味着存在问题。

如果该值可能缺失或存在,并且对于应用程序逻辑均有效,则返回null。

更重要的是:您在代码的其他位置做什么?一致性很重要。


30
@Ken:+1,很高兴提到,如果您确实引发了异常,但是可以先验地检测到它(例如HasItem(...)),那么用户应该提供所说的Has *或Contains方法。
user7116

4
考虑返回Maybe <T>,而不是返回缺少值或返回null的方法。参见mikehadlow.blogspot.nl/2011/01/monads-in-c-5-maybe.html
Erwin Rooijakkers 2015年

2
在@ErwinRooijakkers 设计选择的方式,因为Java 8中,你也可以返回一个可选的<T>
何塞Andias

2
我发现每个答案都令人不安,它回荡着同样的谚语:“如果例外,那就扔。” 大多数工程师将熟悉此原理。我认为,这里真正的问题是如何确定是否认为是例外。OP正在寻求有关存储库模式之类的最佳实践。给定主键的对象不存在通常被认为是例外的吗?是的,这是他的领域将决定的事情,但是大多数具有多年经验的专家建议什么?那就是我们应该看到的答案的类型。
美眉

4
@crush我认为指导原则是将什么参数传递给函数。如果您传递身份(如您提到主键),则如果未找到该主键,则应将其视为例外,因为它表示系统中的状态不一致。示例:GetPersonById(25)如果该人已被删除将抛出该异常,但GetPeopleByHairColor("red")将返回空结果。因此,我认为参数说明了期望值。
约翰·努普

98

仅当确实是错误时才抛出异常。如果该对象的预期行为不存在,则返回null。

否则,这是一个优先事项。


4
我不同意 您可以将异常作为状态码抛出:“ NotFoundException”
ACV

当然,这不是优先考虑的问题。这就是我们最终导致代码不一致的原因-如果不在您团队的代码之中,那么几乎可以肯定的是,当其他开发人员代码与您自己的代码(外部库)交织在一起时。
粉碎

4
我认为处理空情况比“ NotFoundException”要容易得多。考虑一下您必须为抛出“ NotFoundException”的每个单个检索请求编写多少行try-catch。在我看来,准备好所有这些代码很痛苦。
visc

我想引用托尼·霍尔(Tony Hoare)的话:“我称之为我十亿美元的错误”。我不会返回null,要么抛出异常并正确处理它,要么返回一个空对象。
七点

70

通常,如果方法应始终返回对象,则应使用异常。如果您预计偶尔会出现null并希望以某种方式进行处理,请使用null。

无论您做什么,我都强烈建议您不要使用第三个选项:返回一个字符串“ WTF”。


再加上一个,因为在过去的好日子里,我做了几次以上作为快速的肮脏的“临时”修复……这不是个好主意。特别是如果您是学生,将对其进行审查。
rciafardone 2014年

14
因为WTF选项对我而言真是太棒了,所以我本来要投反对票。但是显然我有一个发自内心的
感觉

5
抛出新的WtfExcepti😲n–
Kamafeather

我认为,如果此答案讨论方法总是返回对象而不是“偶尔为null”的原因,将很有帮助。是什么原因导致这种情况?举例说明何时可能存在这种情况。
粉碎

51

如果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

1
如果C#提供了真正的元组,这将是一个更有用的建议,因此我们可以避免使用[out]参数。不过,这是首选模式,因此+1。
Erik Forbes

2
我认为,尝试方法是最好的方法。如果您不能返回该对象,那么您不必查找然后会发生什么。使用Try方法,您立即知道该怎么做。
OregonGhost

让我想起了findfindOrFail从Laravel雄辩
vivoconunxino

@ErikForbes我知道您的评论很旧,但是答案不是要定义从该TryFindObject方法返回的多属性对象吗?对于不想花时间定义封装多个值的对象的程序员来说,元组似乎更是一种懒惰的范例。基本上,所有元组都是核心。
暗恋

@crush-命名为Tuple字面量的IMO。这是带有元组的异步Try Get模式的链接。stackoverflow.com/questions/1626597/…–
ttugates

26

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

  1. 返回null
  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。
marcovtwout 2015年

5
这其中大部分可以更清晰地写有Expected<T> findObject(String)其中Expected<T>具有的功能orNull()orThrow()orSupplied(Supplier<T> supplier)orDefault(T default)。这从错误处理中提取了数据的获取
WorldSEnder 2015年

直到现在我都不知道Expected <T>。我似乎很新,当我写原始答案时可能还不存在。也许您应该将您的评论作为一个正确的答案。
Lena Schimmel,2015年

另外,Expected <T>是C ++模板。是否有其他面向对象语言的实现?
Lena Schimmel,2015年

在Java 8中,返回Optional <T>(在其他语言中称为Maybe <T>等)也是一个选项。这清楚地向调用者表明不返回任何内容是可能的,并且如果调用者未处理该可能性,则不会编译,而不是null(无论如何在Java中),即使调用者不检查它也将进行编译。
有些盖伊

18

使用空对象模式或引发异常。


这是真正的答案。返回null是懒惰的程序员的可怕习惯。
jeremyjjbrown

我不敢相信这个答案还没有被投票给最高职位。这是真正的答案,这两种方法都不容易,并且节省了大量代码膨胀或NPE。
班恩


3
如果使用空对象模式,那么如何区分将键映射到空对象的情况和没有键的情况呢?我认为返回无意义的对象将比返回null更糟糕。向不准备处理它的代码返回null通常会导致引发异常。尽管不是异常的最佳选择,但还是有例外。返回无意义的对象更有可能导致代码错误地认为无意义的数据是正确的。
2014年

3
空对象如何进行实体查找?例如,Person somePerson = personRepository.find("does-not-exist");假设此方法为ID返回空对象does-not-exist。那么正确的行为是somePerson.getAge()什么呢?目前,我还不确定空对象模式是实体查找的正确解决方案。
阿卜杜勒2015年


13

引发异常的优点:

  1. 您的调用代码中的控制程序更简洁。 检查null会注入条件分支,该分支由try / catch本地处理。检查null并不表示您要检查的是什么-您是否正在检查null是因为您正在寻找期望的错误,还是正在检查null,因此您不会在下链进一步传递它?
  2. 消除了“ null”的含义的歧义。 null代表错误还是null实际存储在值中?很难说何时只有一件事可以基于这种决心。
  3. 改进了应用程序中方法行为之间的一致性。 异常通常在方法签名中公开,因此您更能够理解应用程序中方法所占的优势,以及应用程序可以以可预测的方式对哪些信息作出反应。

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


2
+1是因为点2非常好-null的含义与未找到的含义相同。动态语言时,这变得更加重要了空实际上可能是由函数检索存储的对象/ -在这种情况下
亚当Terrey

1
点2的+1。null是否是错误?给定密钥存储的内容是否为null?是否为null表示密钥不存在?这些是真正的问题,可以清楚地表明,返回null几乎永远是不正确的。这可能是最好的答案,因为其他所有人都在抛出模棱两可的“如果异常,则抛出”
粉碎

12

这取决于您的语言和代码是否能促进:LBYL(飞跃之前先看一下)或EAFP(更容易要求宽恕而不是允许)

LBYL说您应该检查值(因此返回一个空值)
EAFP说只是尝试操作并查看操作是否失败(引发异常)

尽管我在上面表示同意。.异常/错误情况应使用异常,使用检查时最好返回null。


EAFP与LBYL在Python中的关系:
http ://mail.python.org/pipermail/python-list/2003-May/205182.html (Web存档


3
在某些情况下,EAFP是唯一有意义的方法。例如,在并发的地图/词典中,无法询问请求映射时是否将存在映射。
2014年

11

只是问问自己:“找不到对象是一种特殊情况吗?” 如果预期在程序的正常过程中发生这种情况,则可能不应引发异常(因为这不是异常行为)。

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

阿兰


5

例外与按合同设计有关。

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

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

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

is_present(key)
find(key) throws Exception

如果并且仅当您提供第二个合同的两种方法时,您才可以抛出异常,因为找不到任何东西!


4

我更喜欢只返回一个null,然后依靠调用方对其进行适当处理。(由于缺少更好的词)(例外)是,如果我绝对“确定”此方法将返回一个对象。在那种情况下,失败是应该并且应该抛出的例外。


4

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

如果状态正常,则返回null。这只是偶尔会发生的事情,呼叫者应进行检查。

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

最终,两者都会起作用,尽管大多数人普遍认为,只有在发生异常的情况下才使用Exceptions是一个好习惯。


2
您将如何阐述“ 正常状态 ”语句,以及将使用什么标准来将其与错误区分开来。
user1451111

4

这里还有更多建议。

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

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

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


3

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

..., bool verify = true)

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


3

返回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)) { ...

3

仅指的是不将null视为异常行为的情况,我肯定是使用try方法的,很明显,不需要像这里所说的那样“读书”或“在跳动之前先看一下”

所以基本上:

bool TryFindObject(RequestParam request, out ResponseParam response)

这意味着用户的代码也将很清楚

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

2

如果了解客户端代码之间的区别很重要,并且这应该是常规行为,那么最好返回null。客户代码然后可以决定要做什么。


2

通常,它应该返回null。调用该方法的代码应决定是引发异常还是尝试其他事情。


2

或退回期权

一个选项基本上是一个容器类,它迫使客户处理展位情况。Scala有这个概念,请查看它的API。

然后,在此对象上具有诸如T getOrElse(T valueIfNull)之类的方法,它们要么返回找到的对象,要么返回客户端指定的名称。


2

首选返回null-

如果调用者在未检查的情况下使用它,则无论如何都会发生异常。

如果来电者并没有真正使用它,不含税他try/ catch


2

不幸的是,JDK是不一致的,如果您尝试访问资源束中不存在的键,则不会发现异常,并且当您从map请求值时,如果不存在该值,则会得到null。因此,如果发现的值可以为null,那么我将更改赢家的答案,如果找不到,则引发异常,否则返回null。因此,请遵循带有一个例外的规则,如果您需要知道为什么找不到值,请始终提出例外,或者。


1

只要应该返回对对象的引用,则返回NULL应该很好。

但是,如果返回的是满是血腥的东西(就像在C ++中那样:'return blah;'而不是'return&blah;'(或'blah'是一个指针)),那么您将无法返回NULL,因为它是在这种情况下,抛出异常或返回没有设置成功标志的空白对象就是我要解决的问题。


1

不要以为任何人都提到了异常处理的开销-需要额外的资源来加载和处理异常,因此,除非它是真正的应用程序终止或进程停止事件(前进会造成弊大于利),否则我会选择传递一个调用环境可以认为合适的值。


1

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

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


1

返回一个null,异常恰好是:您的代码执行了某些意外的操作。



1

如果该方法返回一个集合,则返回一个空集合(如上述)。但请不要使用Collections.EMPTY_LIST之类的!(对于Java)

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

  1. 如果该方法应始终找到结果,并且是找不到对象的真正异常情况,则应引发异常(在Java中:请未经检查的Exception)
  2. (仅限Java)如果可以忍受该方法引发检查异常,则引发项目特定的ObjectNotFoundException或类似事件。在这种情况下,如果您忘记处理异常,编译器会告诉您。(这是我对Java中找不到的东西的首选处理。)
  3. 如果您说没关系,如果找不到对象,并且您的方法名称类似于findBookForAuthorOrReturnNull(..),则可以返回null。在这种情况下,强烈建议您使用某种静态检查或编译器检查,以防止在没有空检查的情况下取消引用结果。在Java的情况下可以是例如。FindBugs(请参阅http://findbugs.sourceforge.net/manual/annotations.html上的 DefaultAnnotation )或IntelliJ-Checking。

如果您决定返回null,请当心。如果您不是项目中唯一的程序员,则在运行时将获得NullPointerExceptions(使用Java或其他语言)。因此,请勿返回在编译时未检查的null。


如果代码编写正确,则不是这样null。查看更多投票答案。
安德鲁·巴伯

但仅当您确保在编译时检查所有空值时。这可以通过在包级别使用FindBugs @NutNull并将您的方法标记为“可能返回null”来完成。或使用Kotlin或Nice之类的语言。但是不返回null更简单。
iuzuz 2012年

也许 “简单” 。但通常通常都是不正确的
安德鲁·巴伯

1
再次:阅读投票最多的答案以获取更多信息。基本上:如果无法找到所请求的书是一种可能的结果,则例外所请求的书是根本就没有找到所请求的书错误的选择,这与发生某些错误相反。
安德鲁·巴伯

1
您在这里误会很多。您正在普遍应用条件建议,这几乎总是一件坏事。也阅读其余的投票表决答案。您的回答仅说明了一个绝对值,并给出了极其错误的逻辑。
Andrew Barber 2012年

1

如果您使用的是引发异常的库或其他类,则应将其重新抛出。这是一个例子。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();
    }
}
}

范例.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'。
有些盖伊

0

这实际上取决于您是否希望找到对象。如果您遵循这样的学派,那就应该使用异常来表示某些东西,好吧,错了,异常发生了,那么:

  • 找到对象;返回对象
  • 找不到对象;抛出异常

否则,返回null。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.