函数应该返回null还是空的对象?

从函数返回数据的最佳做法是什么? 返回一个Null或一个空的对象是更好的吗? 为什么要一个人做一个呢?

考虑一下:

public UserEntity GetUserById(Guid userId) { //Imagine some code here to access database..... //Check if data was returned and return a null if none found if (!DataExists) return null; //Should I be doing this here instead? //return new UserEntity(); else return existingUserEntity; } 

让我们假装在这个程序中会有一个有效的例子,那就是那个GUID中的数据库里没有用户信息。 我会想象在这种情况下抛出exception是不合适的? 另外我也觉得exception处理会影响性能。

如果您打算指出没有数据可用,那么返回null通常是最好的办法。

一个空对象意味着数据已经被返回,而返回null清楚地表明没有返回任何东西。

此外,如果尝试访问对象中的成员,那么返回null将导致nullexception,这对于突出显示错误代码非常有用 – 尝试访问无任何事情的成员是没有意义的。 访问一个空对象的成员不会失败,这意味着错误可能不被发现。

这取决于什么是最适合你的情况。

返回null是否有意义,例如“没有这样的用户存在”?

或者是否有意义创build一个默认用户? 这是最有意义的,当你可以安全地假设,如果一个用户不存在,调用代码打算存在,当他们要求它。

或者,如果调用代码要求用户使用无效的ID,抛出exception(la“FileNotFound”)是否有意义?

但是,从关注点/ SRPangular度分离,前两个更正确。 在技术上 ,第一个是最正确的(但只有一个头发) – GetUserById应该只负责一件事 – 获取用户。 处理自己的“用户不存在”的情况下,通过返回别的东西可能会违反SRP。 如果你select抛出一个exception,分离成不同的check- bool DoesUserExist(id)将是合适的。

基于以下广泛的评论 :如果这是一个API级别的devise问题,这种方法可能类似于“OpenFile”或“ReadEntireFile”。 我们从某个存储库“打开”一个用户,并从结果数据中提取对象。 在这种情况下,例外可能是合适的。 它可能不是,但可能是。

所有的方法都是可以接受的 – 这只取决于API /应用程序的更大的上下文。

我个人使用NULL。 它清楚地表明没有数据要返回。 但有些情况下, 空对象可能是有用的。

如果你的返回types是一个数组,那么返回一个空数组,否则返回null。

你应该抛出一个exception(只),如果一个特定的合同被打破。
在你的具体例子中,基于一个已知的Id请求一个UserEntity,这将取决于事实,如果缺less(删除)的用户是一个预期的情况。 如果是这样,那么返回null但如果不是预期的情况,则抛出一个exception。
请注意,如果该函数被称为UserEntity GetUserByName(string name)它可能不会抛出,但返回null。 在这两种情况下返回一个空的UserEntity将是无益的。

对于string,数组和集合,情况通常是不同的。 我记得一些MS的指导方针,方法应该接受null作为一个“空”列表,但返回零长度的集合,而不是null 。 string也一样。 请注意,您可以声明空数组: int[] arr = new int[0];

这是一个业务问题,取决于具有特定Guid Id的用户的存在是否是该function的预期正常使用情况,还是防止应用程序成功完成此方法向用户提供的任何function的exception反对…

如果这是一个“例外”,那么缺less具有该Id的用户将阻止应用程序成功完成它正在执行的任何function(例如,我们正在为我们已经将产品发往…的客户创build发票。 ),那么这种情况应该抛出一个ArgumentException(或其他一些自定义exception)。

如果缺less用户可以,(调用此函数的潜在正常结果之一),然后返回空….

编辑:(在另一个答案中解决亚当的评论)

如果应用程序包含多个业务stream程,其中一个或多个业务stream程需要一个用户才能成功完成,而其中的一个或多个业务stream程可以在没有用户的情况下成功完成,则应该将该exception进一步向上调用,需要用户的业务stream程正在调用这个执行线程。 在这个方法和那个点(抛出exception的地方)之间的方法应该只是传达没有用户存在(null,boolean,不pipe – 这是一个实现细节)。

但是,如果应用程序内的所有进程都需要用户,我仍然会在此方法中抛出exception…

我个人会返回null,因为这是我期望的DAL /知识库层的行为。

如果它不存在,不要返回任何可以被解释为成功获取对象的东西, null在这里漂亮地工作。

最重要的是要在你的DAL / Repos Layer上保持一致,这样你就不会对如何使用它感到困惑。

另一种方法涉及传入一个callback对象或委托,将操作该值。 如果找不到值,则不调用callback。

 public void GetUserById(Guid id, UserCallback callback) { // Lookup user if (userFound) callback(userEntity); // or callback.Call(userEntity); } 

当你想要避免在你的代码中进行空的检查,并且没有发现一个值的时候,这个效果并不是错误。 如果您需要任何特殊的处理,您也可以提供一个callback,以便找不到任何对象。

 public void GetUserById(Guid id, UserCallback callback, NotFoundCallback notFound) { // Lookup user if (userFound) callback(userEntity); // or callback.Call(userEntity); else notFound(); // or notFound.Call(); } 

使用单个对象的相同方法可能如下所示:

 public void GetUserById(Guid id, UserCallback callback) { // Lookup user if (userFound) callback.Found(userEntity); else callback.NotFound(); } 

从devise的angular度来看,我真的很喜欢这种方法,但是有一个缺点,那就是呼叫站点体积庞大,而且语言不支持一stream的function。

我们使用CSLA.NET,它认为一个失败的数据提取应该返回一个“空”对象。 这实际上很烦人,因为它需要检查obj.IsNew而不是obj == null的约定。

正如前面提到的海报, null返回值会导致代码立即失败,从而减less由空对象引起的隐形问题的可能性。

就个人而言,我认为null更为优雅。

这是一个非常常见的情况,我很惊讶这里的人们对此感到惊讶:在任何Web应用程序中,数据通常是使用querystring参数获取的,这个参数显然可能会被破坏,所以要求开发人员处理“not found ”。

你可以通过以下方式处理:

 if(User.Exists(id)){
   this.User = User.Fetch(id);
 } else {
  的Response.Redirect( “〜/ notfound.aspx”);
 }

…但这是每次额外的数据库调用,这可能是高stream量页面上的问题。 鉴于:

 this.User = User.Fetch(id);

 if(this.User == null){
  的Response.Redirect( “〜/ notfound.aspx”);
 }

…只需要一个电话。

我更喜欢null ,因为它与null-coalescing运算符( ?? )兼容。

我会说返回null而不是一个空的对象。

但是在这里提到的具体实例,您正在通过用户idsearch用户,这是该用户的关键,在这种情况下,如果没有find用户实例实例,我可能想引发exception。

这是我一般遵循的规则:

  • 如果通过主键操作发现找不到结果,则抛出ObjectNotFoundException。
  • 如果没有find任何其他条件的查找结果,则返回null。
  • 如果没有find任何结果,则通过非关键条件查找可能返回多个对象的返回空集合。

我倾向于

  • 如果事先不知道对象ID是否存在,则return null
  • 如果对象标识不存在,则throw

我用这三种types的方法来区分这两种情况。 第一:

 Boolean TryGetSomeObjectById(Int32 id, out SomeObject o) { if (InternalIdExists(id)) { o = InternalGetSomeObject(id); return true; } else { return false; } } 

第二:

 SomeObject FindSomeObjectById(Int32 id) { SomeObject o; return TryGetObjectById(id, out o) ? o : null; } 

第三:

 SomeObject GetSomeObjectById(Int32 id) { SomeObject o; if (!TryGetObjectById(id, out o)) { throw new SomeAppropriateException(); } return o; } 

它会根据上下文而有所不同,但是如果我正在寻找一个特定的对象(如您的示例),通常会返回null,如果我正在查找一组对象但返回一个空集合,但没有。

如果你在你的代码中犯了一个错误,并且返回null会导致空指针exception,那么你越早捕获就越好。 如果你返回一个空的对象,它的初始使用可能会起作用,但是你以后可能会出错。

在这种情况下最好在没有这样的用户的情况下返回“空”。 也让你的方法是静态的。

编辑:

通常像这样的方法是一些“用户”类的成员,并没有访问其实例成员。 在这种情况下,这个方法应该是静态的,否则你必须创build一个“User”实例,然后调用GetUserById方法,这将返回另一个“User”实例。 同意这是混乱。 但是,如果GetUserById方法是某个“DatabaseFactory”类的成员 – 将它作为实例成员保留就没有问题了。

我个人返回对象的默认实例。 原因是我期望该方法返回零到很多或从零到一(取决于方法的目的)。 使用这种方法的唯一原因是任何types的错误状态,如果该方法没有返回对象,并且总是期望(以一对多或单数返回的方式)。

至于这是一个商业领域的问题的假设 – 我只是没有看到这方面的一面。 返回types的规范化是一个有效的应用程序架构问题。 至less在编码实践中是标准化的主题。 我怀疑有一个商业用户会说“在schemeX中,只是给他们一个空”。

在我们的Business Objects中,我们有两个主要的Get方法:

为了在上下文中保持简单,或者你问他们会是:

 // Returns null if user does not exist public UserEntity GetUserById(Guid userId) { } // Returns a New User if user does not exist public UserEntity GetNewOrExistingUserById(Guid userId) { } 

第一种方法是在获取特定实体时使用,第二种方法专门用于在网页上添加或编辑实体。

这使我们能够在使用它们的环境中获得两全其美的好处。

我是法国的IT学生,所以请原谅我可怜的英语。 在我们的课堂上,我们被告知,这样的方法不应该返回null,也不应该是空的对象。 这个方法的用户应该首先检查他正在寻找的对象是否存在,然后再尝试获取它。

使用Java,我们被要求添加一个assert exists(object) : "You shouldn't try to access an object that doesn't exist"; 在任何可能返回null的方法开始时,expression“前提条件”(我不知道英文中的单词是什么)。

海事组织这真的不容易使用,但这就是我正在使用,等待更好的东西。

如果用户未被发现的情况经常出现,并且您想要根据情况以各种方式处理(有时会抛出exception,有时候会replace空的用户),您也可以使用接近F#的Option或Haskell Maybetypes,它明确地将“无价值”的情况从“find东西!”中分离出来。 数据库访问代码可能如下所示:

 public Option<UserEntity> GetUserById(Guid userId) { //Imagine some code here to access database..... //Check if data was returned and return a null if none found if (!DataExists) return Option<UserEntity>.Nothing; else return Option.Just(existingUserEntity); } 

这样使用:

 Option<UserEntity> result = GetUserById(...); if (result.IsNothing()) { // deal with it } else { UserEntity value = result.GetValue(); } 

不幸的是,每个人似乎都像他们自己的这种types。

我通常返回null。 它提供了一个快速而简单的机制来检测是否有东西被搞乱,而不会抛出exception,并且在整个地方使用大量的try / catch。

对于集合types,我将返回一个空集合,对于其他所有我喜欢使用NullObject模式的types来返回实现与返回types相同接口的对象。 有关模式的详细信息,请查看链接文本

使用NullObject模式,这将是:

 public UserEntity GetUserById(Guid userId) 

{//想象一下这里的一些代码来访问数据库…..

  //Check if data was returned and return a null if none found if (!DataExists) return new NullUserEntity(); //Should I be doing this here instead? return new UserEntity(); else return existingUserEntity; 

}

 class NullUserEntity: IUserEntity { public string getFirstName(){ return ""; } ...} 

把别人的话说得精明

例外情况是特殊情况

如果这个方法是纯粹的数据访问层,我会说,给定一些参数,包括在一个select语句,它会希望我可能找不到任何行build立一个对象,因此返回null是可以接受的,因为这是数据访问逻辑。

另一方面,如果我期望我的参数反映一个主键,我应该只返回一行,如果我有一个以上的回来,我会抛出一个exception。 0可以返回null,2不可以。

现在,如果我有一些针对LDAP提供程序进行检查的login代码,然后对数据库进行检查以获取更多详细信息,并且我希望这些代码应始终保持同步,那么我可能会抛出exception。 正如其他人所说,这是业务规则。

现在我会说这是一个普遍的规则。 有几次你可能想要打破这一点。 然而,我的经验和C#(大量)和Java(稍微有点)的实验告诉我,处理exception要比通过条件逻辑处理可预见的问题昂贵得多。 在某些情况下,我正在谈论2或3个数量级的价格。 所以,如果有可能你的代码可能最终在一个循环,那么我会build议返回null并testing它。

原谅我的伪PHP /代码。

我认为这取决于结果的预期用途。

如果您打算编辑/修改返回值并保存,则返回一个空的对象。 这样,您可以使用相同的函数来填充新的或现有的对象上的数据。

说我有一个函数,它接受一个主键和一个数组数组,填充行的数据,然后将结果logging保存到数据库。 由于我打算用我的数据填充对象,从getter获得一个空对象可能是一个巨大的优势。 这样,我可以在任何情况下执行相同的操作。 无论如何,您都可以使用getter函数的结果。

例:

 function saveTheRow($prim_key, $data) { $row = getRowByPrimKey($prim_key); // Populate the data here $row->save(); } 

在这里我们可以看到,同一系列的操作操作了这种types的所有logging。

但是,如果返回值的最终目的是读取数据并对其进行处理,那么我将返回null。 这样,我可以很快确定是否没有数据返回,并向用户显示适当的消息。

通常情况下,我会捕获我的函数中的exception,以检索数据(所以我可以logging错误消息等),然后直接返回从catch。 对于最终用户来说通常不会有什么问题,所以我最好将错误logging/处理直接封装在获取数据的函数中。 如果你在任何一家大公司维护一个共享的代码库,这是特别有利的,因为即使是最懒惰的程序员也可以强制进行适当的错误logging/处理。

例:

 function displayData($row_id) { // Logging of the error would happen in this function $row = getRow($row_id); if($row === null) { // Handle the error here } // Do stuff here with data } function getRow($row_id) { $row = null; try{ if(!$db->connected()) { throw excpetion("Couldn't Connect"); } $result = $db->query($some_query_using_row_id); if(count($result) == 0 ) { throw new exception("Couldn't find a record!"); } $row = $db->nextRow(); } catch (db_exception) { //Log db conn error, alert admin, etc... return null; // This way I know that null means an error occurred } return $row; } 

这是我的一般规定。 迄今为止效果很好。

有趣的问题,我认为没有“正确”的答案,因为它总是取决于你的代码的责任。 你的方法是否知道没有发现数据是否有问题? 在大多数情况下,答案是“否”,这就是为什么返回null,让处理他的情况是完美的。

也许一个区分抛出方法和返回空方法的好方法是在你的团队中find一个约定:如果没有任何东西可以获得,那么说“获得”某些东西的方法应该抛出一个exception。 可能返回null的方法可能会以不同的方式命名,也许可能是“Find …”。

If the object returned is something that can be iterated over, I would return an empty object, so that I don't have to test for null first.

例:

 bool IsAdministrator(User user) { var groupsOfUser = GetGroupsOfUser(user); // This foreach would cause a run time exception if groupsOfUser is null. foreach (var groupOfUser in groupsOfUser) { if (groupOfUser.Name == "Administrators") { return true; } } return false; } 

I like not to return null from any method, but to use Option functional type instead. Methods that can return no result return an empty Option, rather than null.

Also, such methods that can return no result should indicate that through their name. I normally put Try or TryGet or TryFind at the beginning of the method's name to indicate that it may return an empty result (eg TryFindCustomer, TryLoadFile, etc.).

That lets the caller apply different techniques, like collection pipelining (see Martin Fowler's Collection Pipeline ) on the result.

Here is another example where returning Option instead of null is used to reduce code complexity: How to Reduce Cyclomatic Complexity: Option Functional Type

More meat to grind: let's say my DAL returns a NULL for GetPersonByID as advised by some. What should my (rather thin) BLL do if it receives a NULL? Pass that NULL on up and let the end consumer worry about it (in this case, an ASP.Net page)? How about having the BLL throw an exception?

The BLL may be being used by ASP.Net and Win App, or another class library – I think it is unfair to expect the end consumer to intrinsically "know" that the method GetPersonByID returns a null (unless null types are used, I guess).

My take (for what it's worth) is that my DAL returns NULL if nothing is found. FOR SOME OBJECTS, that's ok – it could be a 0:many list of things, so not having any things is fine (eg a list of favourite books). In this case, my BLL returns an empty list. For most single entity things (eg user, account, invoice) if I don't have one, then that's definitely a problem and a throw a costly exception. However, seeing as retrieving a user by a unique identifier that's been previously given by the application should always return a user, the exception is a "proper" exception, as in it's exceptional. The end consumer of the BLL (ASP.Net, f'rinstance) only ever expects things to be hunky-dory, so an Unhandled Exception Handler will be used instead of wrapping every single call to GetPersonByID in a try – catch block.

If there is a glaring problem in my approach, please let me know as I am always keen to learn. As other posters have said, exceptions are costly things, and the "checking first" approach is good, but exceptions should be just that – exceptional.

I'm enjoying this post, lot's of good suggestions for "it depends" scenarios 🙂

I am perplexed at the number of answers (all over the web) that say you need two methods: an "IsItThere()" method and a "GetItForMe()" method and so this leads to a race condition. What is wrong with a function that returns null, assigning it to a variable, and checking the variable for Null all in one test? My former C code was peppered with

if ( NULL != (variable = function(arguments…)) ) {

So you get the value (or null) in a variable, and the result all at once. Has this idiom been forgotten? 为什么?

I agree with most posts here, which tend towards null .

My reasoning is that generating an empty object with non-nullable properties may cause bugs. For example, an entity with an int ID property would have an initial value of ID = 0 , which is an entirely valid value. Should that object, under some circumstance, get saved to database, it would be a bad thing.

For anything with an iterator I would always use the empty collection. 就像是

 foreach (var eachValue in collection ?? new List<Type>(0)) 

is code smell in my opinion. Collection properties shouldn't be null, ever.

An edge case is String . Many people say, String.IsNullOrEmpty isn't really necessary, but you cannot always distinguish between an empty string and null. Furthermore, some database systems (Oracle) won't distinguish between them at all ( '' gets stored as DBNULL ), so you're forced to handle them equally. The reason for that is, most string values either come from user input or from external systems, while neither textboxes nor most exchange formats have different representations for '' and null . So even if the user wants to remove a value, he cannot do anything more than clearing the input control. Also the distinction of nullable and non-nullable nvarchar database fields is more than questionable, if your DBMS is not oracle – a mandatory field that allows '' is weird, your UI would never allow this, so your constraints do not map. So the answer here, in my opinion is, handle them equally, always.

Concerning your question regarding exceptions and performance: If you throw an exception which you cannot handle completely in your program logic, you have to abort, at some point, whatever your program is doing, and ask the user to redo whatever he just did. In that case, the performance penalty of a catch is really the least of your worries – having to ask the user is the elephant in the room (which means re-rendering the whole UI, or sending some HTML through the internet). So if you don't follow the anti-pattern of " Program Flow with Exceptions ", don't bother, just throw one if it makes sense. Even in borderline cases, such as "Validation Exception", performance is really not an issue, since you have to ask the user again, in any case.

I think functions should not return null, for the health of your code-base. I can think of a few reasons:

There will be a large quantity of guard clauses treating null reference if (f() != null) .

What is null , is it an accepted answer or a problem? Is null a valid state for a specific object? (imagine that you are a client for the code). I mean all reference types can be null, but should they?

Having null hanging around will almost always give a few unexpected NullRef exceptions from time to time as your code-base grows.

There are some solutions, tester-doer pattern or implementing the option type from functional programming.

You should be throwing an exception if it is an exceptional circumstance that you call that code with an invalid user ID. If it is NOT an exceptional circumstance, then what you are essentially doing is using a "getter" method to test whether a user exists or not. That is like trying to open a file to see if it exists or not (lets stick to c#/java here) instead of using the exists method, or trying to access dictionary elements and seeing if they exist by looking at the return value instead of using the "contains" method first.

Therefore, it is likely you are after an additional method such as "exists" to first check if there is such a user. Performance of exceptions is definitely not a reason to just not use them at all unless you have genuine performance issues.