检索方法是否应该返回“null”或在无法生成返回值时引发异常?[已结束]

Should a retrieval method return 'null' or throw an exception when it can't produce the return value? [closed]

提问人: 提问时间:10/7/2008 最后编辑:7 revs, 5 users 39%Robert 更新时间:8/6/2022 访问量:194391

问:


想改进这个问题吗?更新问题,以便可以通过编辑这篇文章来用事实和引文来回答。

7年前关闭。

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

如果找不到,我应该:

  1. 返回 null
  2. 引发异常
  3. 其他

哪个是最好的做法或成语?

Java 异常 错误处理 null

评论

65赞 Rik 10/7/2008
无论你做什么,一定要记录下来。我认为这一点比确切哪种方法是“最好的”更重要。
7赞 Teddy 10/2/2009
这取决于编程语言的流行习语。请用编程语言标签标记这个问题。
3赞 3k- 8/20/2013
返回 null 可能只意味着成功或失败,这通常不是太多信息(某些方法可能在许多方面失败)。库应该更好地抛出异常以明确错误,这样主程序就可以决定如何在更高级别上处理错误(与内置错误处理逻辑相反)。
5赞 crush 8/10/2018
在我看来,真正的问题是,我们是否应该认为找不到一个实体是例外的,如果是这样,为什么?没有人真正充分回答如何得出这个结论,现在问答已经结束。很遗憾,该行业尚未就这一重要话题达成共识。是的,我知道这要看情况。因此,请解释为什么它不仅仅取决于“如果例外,请扔掉”

答:

4赞 swilliams #1

我更喜欢只返回一个 null,并依靠调用方来适当地处理它。(因为缺乏更好的词)例外是,如果我绝对“确定”此方法将返回一个对象。在这种情况下,失败是例外的,应该而且应该抛出。

13赞 2 revs, 2 users 67%dmckee #2

与您正在使用的 API 保持一致。

20赞 2 revspmlarocque #3

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

评论

0赞 jeremyjjbrown 5/24/2013
这才是真正的答案。返回 null 是懒惰程序员的一个可怕习惯。
0赞 Bane 9/6/2013
我简直不敢相信这个答案还没有被投票到最高。这才是真正的答案,无论哪种方法都非常简单,可以节省大量代码膨胀或 NPE。
3赞 supercat 10/5/2014
如果使用 null 对象模式,如何区分键映射到 null 对象的情况和键没有映射的情况?我认为返回一个无意义的对象比返回 null 要糟糕得多。将 null 返回给未准备好处理它的代码通常会导致引发异常。不是例外的最佳选择,但仍然是一个例外。返回无意义的对象更有可能导致代码错误地将无意义数据视为正确数据。
3赞 Abdull 6/15/2015
null 对象在实体查找中的行为如何?例如,假设此方法返回 ID 的 null 对象。那么正确的行为是什么?目前,我还不相信 null 对象模式是实体查找的正确解决方案。Person somePerson = personRepository.find("does-not-exist");does-not-existsomePerson.getAge()
2赞 plinth #4

如果客户端代码必须知道 found 和 not found 之间的区别,并且这应该是一种例行行为,那么最好返回 null。然后,客户端代码可以决定要执行的操作。

2赞 Eran Galperin #5

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

0赞 Rob #6

这实际上取决于你是否希望找到这个对象。如果你遵循一个思想流派,即应该使用异常来表示某事,那么,呃,异常已经发生了:

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

否则,返回 null。

111赞 Carlton Jenke #7

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

否则,这是一个偏好问题。

评论

4赞 ACV 10/16/2017
我不同意。可以将异常作为状态代码抛出:“NotFoundException”
0赞 crush 8/10/2018
这当然不应该是一个偏好问题。这就是我们最终得到不一致代码的原因 - 如果不是在你团队的代码中,那么几乎可以肯定的是,当其他开发人员的代码与你自己的代码(外部库)交织在一起时。
8赞 visc 8/10/2018
我认为处理 null 大小写比“NotFoundException”容易得多。想一想,你必须围绕每个抛出“NotFoundException”的 retrival 请求编写多少行 try-catch......在我眼中准备好所有这些代码是很痛苦的。
2赞 seven 9/9/2019
我想引用托尼·霍尔(Tony Hoare)的话:“我称之为我的十亿美元错误”。我不会返回 null,我要么抛出异常并正确处理它,要么返回一个空对象。
0赞 Thomas W 6/10/2022
@visc 不要发现无法解决的异常!如果这是一个错误的情况,请将其传播到顶层并失败请求/ UI操作。最好的做法是经常投掷,很少接住。literatejava.com/exceptions/......
528赞 Ken #8

如果您总是期望找到一个值,那么如果缺少该值,则引发异常。例外意味着存在问题。

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

更重要的是:你在代码的其他地方做了什么?一致性很重要。

评论

31赞 user7116 10/7/2008
@Ken:+1,值得一提的是,如果您确实抛出异常,但可以先验地检测到它(如 HasItem(...)),那么用户应该提供所述 Has* 或 Contains 方法。
4赞 user2609980 9/29/2015
当值缺失时,不要返回值或 null,而是考虑返回 Maybe<T>。请参见 mikehadlow.blogspot.nl/2011/01/monads-in-c-5-maybe.html
2赞 José Andias 1/27/2016
从 Java 8 开始,以@ErwinRooijakkers设计选择方式,您还可以返回 Optional<T>
5赞 crush 8/10/2018
我觉得有点令人不安,每个答案都呼应着同一句谚语:“如果它很特别,就扔掉。大多数工程师都会熟悉这个原理。在我看来,这里真正的问题是如何确定它是否应该被视为例外。例如,OP 正在寻找有关存储库模式之类的最佳实践。如果给定主键,则对象不存在通常被认为是例外情况吗?是的,这是他的领域将决定的事情,但大多数具有多年经验的专家有什么建议?这就是我们应该看到的答案。
6赞 John Knoop 11/1/2018
@crush我认为指导原则是将哪些参数传递给函数。如果传递标识(例如提及主键),则如果未找到该项,则应将其视为异常,因为它表示系统中的状态不一致。示例:如果该人已被删除,则会抛出,但会返回一个空结果。所以,我认为这些参数说明了一些期望。GetPersonById(25)GetPeopleByHairColor("red")
85赞 Matias Nino #9

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

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

评论

0赞 rciafardone 3/13/2014
另外一个,因为在过去美好的日子里,我不止几次这样做,作为一个快速肮脏的“时间”修复......没有好主意。特别是如果你是学生,它要被审查。
24赞 swestner 9/24/2015
我打算投反对票,因为 WTF 选项对我来说似乎很棒......但显然我有一颗心
20赞 Kamafeather 1/16/2018
抛出新的 WtfExcepti😲n
0赞 crush 8/10/2018
我认为,如果这个答案谈到为什么方法总是返回一个对象而不是“偶尔返回一个 null”的原因,那将会很有帮助。是什么导致了这种情况?举例说明何时可能存在这种情况。
4赞 Steve B. #10

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

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

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

最终,这两种方法都会起作用,尽管大多数人通常认为只有在发生异常时才使用异常是一种很好的做法。

评论

2赞 user1451111 6/14/2018
您将如何详细说明“正常事态”陈述,以及您将使用什么标准将其与错误区分开来。
0赞 Mateusz Stępniak 11/29/2021
@user1451111,我发现了一个关于软件工程的相关讨论,关于什么是正常状态
1赞 warren #11

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

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

3赞 3 revsAtes Goral #12

返回 null 而不是引发异常,并在 API 文档中清楚地记录 null 返回值的可能性。如果调用代码不遵循 API 并检查 null 大小写,则无论如何都可能导致某种“null 指针异常”:)

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

选项 A

Object *findObject(Key &key);

找不到对象时返回 null。很好,很简单。我会选择这个。下面的替代方法适用于不讨厌 out-params 的人。

选项 B

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

传入对将接收对象的变量的引用。当找不到对象时,该方法将引发异常。如果一个对象不是真的被找到,那么这个约定可能更合适——因此你抛出一个异常来表示这是一个意外的情况。

选项C

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

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

if (!findObject(myKey, myObj)) { ...
1赞 ScottCher #13

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

1赞 GBegen #14

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

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

52赞 3 revsKevin Gale #15

如果 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赞 Erik Forbes 10/7/2008
如果 C# 提供了真正的元组,这将是一个更有用的建议,因此我们可以避免使用 [out] 参数。不过,这是首选模式,所以 +1。
3赞 OregonGhost 10/7/2008
在我看来,尝试方法是最好的方法。您不必查找如果无法返回对象会发生什么情况。使用 Try 方法,您可以立即知道该怎么做。
0赞 javier_domenech 5/31/2018
让我想起了 Laravel EloquentfindfindOrFail
0赞 crush 8/10/2018
@ErikForbes我知道您的评论很旧,但答案不是定义一个将从方法返回的多属性对象吗?对于不想花时间定义封装多个值的对象的程序员来说,元组似乎更像是一种懒惰的范式。也就是说,无论如何,所有元组都是核心。TryFindObject
0赞 ttugates 4/9/2019
@crush - 命名元组文字是一种选择,IMO。这是指向包含元组的异步 Try Get 模式的链接。stackoverflow.com/questions/1626597/......
2赞 John Nilsson #16

或返回 Option

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

然后,你在这个对象上有像 T getOrElse(T valueIfNull) 这样的方法,要么返回找到的对象,要么返回客户端指定的 allterion。

1赞 Anders #17

返回一个 null,异常就是这样:你的代码做了一些意想不到的事情。

13赞 3 revs, 2 users 93%Corey Goldberg #18

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

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

虽然我同意上面的观点。异常应用于异常/错误条件,使用检查时最好返回 null。


Python 中的 EAFP 与 LBYL:
http://mail.python.org/pipermail/python-list/2003-May/205182.htmlWeb 存档
)

评论

3赞 supercat 10/5/2014
在某些情况下,EAFP 是唯一有意义的方法。例如,在并发映射/字典中,无法在请求映射时询问映射是否存在。
3赞 Lev #19

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

..., bool verify = true)

True 表示抛出,false 表示返回一些错误返回值。这样,使用此功能的人就有这两种选择。默认值应为 true,以方便那些忘记错误处理的人。

13赞 AlanR #20

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

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

-艾伦。

1赞 Andrew Lewis #21

例外情况应为例外情况。如果返回 null 有效,则返回 null

评论

1赞 Martin Cote 10/7/2008
或多或少是真的。查看 boost.org/community/error_handling.html
6赞 akuhn #22

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

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

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

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

is_present(key)
find(key) throws Exception

当且仅当您提供第二个合约的两种方法时,您才被允许抛出异常,因为什么都没有找到!

4赞 2 revs, 2 users 92%Duncan #23

这里还有一些建议。

如果返回集合,请避免返回 null,返回空集合,这样可以使枚举更易于处理,而无需先进行 null 检查。

一些 .NET API 使用 thrownOnError 参数的模式,该参数为调用方提供了选择,如果找不到对象,则是否真的是异常情况。Type.GetType 就是一个示例。BCL 的另一种常见模式是 TryGet 模式,其中返回布尔值并通过输出参数传递值。

在某些情况下,您还可以考虑 Null 对象模式,该模式可以是默认模式,也可以是没有行为的版本。关键是避免在整个代码库中进行空检查。有关详细信息,请参阅此处 链接

27赞 Lena Schimmel #24

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

  1. 返回 null
  2. 抛出异常
  3. 使用 null 对象模式
  4. 提供 Boolean 参数 to you 方法,以便调用方可以选择是否希望你引发异常
  5. 提供一个额外的参数,以便调用方可以设置一个值,如果找不到值,他将返回该值

或者,您可以组合以下选项:

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

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

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

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

评论

0赞 marcovtwout 7/14/2015
我喜欢清晰的函数名称,尤其是 orCreate 和 orDefault。
5赞 WorldSEnder 10/6/2015
其中大部分可以用 where has functions , , , .这将从错误处理中抽象出数据的获取Expected<T> findObject(String)Expected<T>orNull()orThrow()orSupplied(Supplier<T> supplier)orDefault(T default)
0赞 Lena Schimmel 10/6/2015
直到现在我才知道 Expected<T>。我似乎很新,当我写原始答案时可能不存在。也许你应该把你的评论作为一个正确的答案。
0赞 Lena Schimmel 10/6/2015
此外,Expected<T> 是一个C++模板。在其他面向对象的语言中也有它的实现吗?
0赞 Some Guy 1/22/2019
在 Java 8 中,返回 Optional<T>(在其他语言中称为 Maybe<T> 等)也是一个选项。这清楚地向调用者表明,不返回任何内容是一种可能性,如果调用者没有处理这种可能性,则不会编译,而不是 null,即使调用者不检查它,它(无论如何在 Java 中)也会编译。
3赞 DorD #25

仅指 null 不被视为异常行为的情况,我绝对支持 try 方法,很明显,没有必要像这里所说的那样“读书”或“在跳跃之前先看”

所以基本上:

bool TryFindObject(RequestParam request, out ResponseParam response)

这意味着用户的代码也将一目了然

...
if(TryFindObject(request, out response)
{
  handleSuccess(response)
}
else
{
  handleFailure()
}
...
2赞 kizzx2 #26

首选返回 null --

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

如果来电者没有真正使用它,请不要向他征税 a / blocktrycatch

2赞 Dmitriy R #27

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

18赞 Clifford Oravec #28

抛出异常的优点:

  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赞 Adam Terrey 10/13/2015
+1,因为第 2 点非常好 - null 与 not found 的含义不同。在处理动态语言时,这一点变得更加重要,其中 Null 实际上可能是函数存储/检索的对象 - 在这种情况下
1赞 crush 8/10/2018
+1 表示点 #2。null 是错误吗?为给定密钥存储的内容为 null 吗?null 是否表示密钥不存在?这些是真正的问题线,清楚地表明返回 null 几乎永远不会正确。这可能是这里最好的答案,因为其他人都在抛出模棱两可的“如果它是例外的,那就扔掉”
0赞 tkruse 11/19/2020
但是,与返回相比,异常可能会产生性能损失(例如在 Java 中)。未经检查的异常(例如在 Java 中)可能会出现在意想不到的地方。
2赞 2 revsiuzuz #29

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

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

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

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

评论

0赞 Andrew Barber 8/21/2012
如果代码编写得正确,则不会。查看得票最多的答案以了解更多信息。null
0赞 iuzuz 8/21/2012
但前提是在编译时确保检查所有 null。这可以通过在包级别使用 FindBugs @NutNull 来完成,并将方法标记为“可能返回 null”。或者使用 Kotlin 或 Nice 等语言。但是不返回 null 要简单得多。
0赞 Andrew Barber 8/21/2012
“更简单”,也许。但往往完全不正确
1赞 Andrew Barber 8/21/2012
再次:阅读得票最高的答案以获取更多信息。基本上:如果找不到请求的书是潜在的预期结果,那么当根本找不到请求的书时,异常是错误的做法,而不是发生一些错误。
1赞 Andrew Barber 8/22/2012
你在这里误解了很多。你正在普遍应用有条件的建议,这几乎总是一件坏事。也可以阅读其余的投票答案。你的回答只是陈述了一个绝对的,并为它提出了极其错误的逻辑。
1赞 svlzx #30

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

主 .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;
        }

    }
}

例子2:.java

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

}

评论

0赞 Some Guy 1/22/2019
如果从函数中抛出的异常不是运行时异常,并且调用方需要处理它(而不仅仅是终止程序),那么与其从调用者无法预料的内部子系统抛出异常,不如用外部检查异常包装内部异常, 同时“链接”内部异常,以便调试人员可以找出引发外部异常的原因。例如,example1 可以使用 'throw new MyCheckedException(“Please set the \”obj\“”, e)' 在抛出的异常中包含 'e'。