A common question you may frequently ask yourself is: Should I return null or empty values from a method?
Returning null is common programming practice used when a method doesn’t have a specific value to return. However, if you do not handle the null result that the method returns, it can cause bugs in the caller code. But is it a good idea to return a null value from a method?
As a general rule, avoid returning a null value from a method whenever possible. Returning a null value confuses your fellow developers and may lead to unpredictable program behavior and potential security vulnerabilities. In short, never return a null value from any method of your application.
The good news is that there are ways to handle such mistakes. This article discusses many important points about returning null or empty values, including why returning null is a bad idea, when you should return null or empty values, and three ways to avoid returning null values. Let’s dig into more details.
Should I return null or empty values from a method?
When you deal with the scenario of not being able to return a specific value, you will have to return either an empty or null value based on the functionality of your method. For example, refer to the following simple method:
public Product GetProduct(int id)
{
var product = _db.Products.GetData(k => k.ProductId == id);
return product;
}
The GetProduct method searches through the product database and returns the product object. The GetData method returns null if no product is found in the database.
It is best practice to return empty values rather than null ones. Especially when you return a collection, enumerable, or an object, you should avoid returning null. Returning null is okay if your code handles the returning null value. But developers may forget to write a null check.
Thus, method callers could become victims of unhandled null values.
But if the method callers try to access the empty values, there will be no big issue as you do not need special handling.
Null reference – a billion-dollar mistake
The null reference is often called the “worst mistake in software development”.
The null reference was invented in 1965 by Tony Hoare when he was designing the “first comprehensive type system for references in Object Oriented Language(OOP).” Since he found it easy to implement, he introduced the null reference.
However, at a conference in 2009, Tony Hoare apologized for inventing it. He says, “This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.”
Here is the thing: the method caller might not be aware that it returns a null.
For example, suppose you return a null object, and the method callers try to access the methods of the null object. The result is exceptions in the caller code if you haven’t explicitly handled it in your code.
Different programming languages throw similar null exceptions. For example, In C#, returning a null value leads to “NullReferenceException”; in Java, this leads to NullPointerException, etc.
In a system with different complicated integrations, such unhandled null references can have ripple effects, ultimately losing the collect functionality of the related features.
Even Robert C. Martin, in his book “The Clean Code“, a widely used handbook about better software craftsmanship, suggests avoiding returning null values.
“Don’t return NULL – When we return null, we are essentially creating work for ourselves and foisting problems for our callers. All it takes is one missing null check to send an application spinning out of control.”
Therefore, you need to define the error handling code with the method or let callers be aware of it and force them to handle that exception before it becomes a million-dollar mistake.
When should you return null from a method?
Although returning null is considered a bad practice, you may consider returning null if the sole purpose of your method is to show that there are no data and use it for display purposes rather than more functional tasks. For example, let’s say you want to write a helper or utility function to check if an employee object has medical claims. You may return a null if the method cannot find any claim and display a notification that there are no medical claims.
Also, if the method used does not go beyond the declared file, it is less likely to cause problems. However, in real software development, those are rare cases. Therefore, it is always a good idea to return a default or an empty value instead of returning a null.
When should you return empty values?
Returning empty values is best recommended for methods that return a set of values from a collection or enumerable or return an object.
Like in the following C# code example, return an empty array when you want to return a null value so that the calling codes can better handle the empty array without being interrupted by null exceptions. Also, when returning empty arrays, do not forget to restrict access elements in the empty array to void IndexOutOfRangeException.
public static string[] EmptyArray
{
get
{
return new string[0];
}
}
C# also has the Enumerable.Empty method that you use to indicate the method returns an empty collection.
How to avoid returning a null value?
Programming languages provide several techniques to minimize the likelihood of using null values. Lets’ see how you can avoid returning null values taking C# as the example.
#1 Eliminate nulls with C# Nullable reference types
Nullable reference types were introduced in C# 8.0 to lower the likelihood of causing the NullReferenceException in a code. It provides three useful features for the developers to avoid returning null values.
Ability to decide if a variable is null before dereferencing it
When a program compiles, it performs static flow analysis. If it encounters nullable references, it may throw warnings indicating that there is a possibility to throw NullReferenceException with null variables. Programmers can then fix these warnings by avoiding returning null values. For example,
string firstValue = null;
// warning
Console.WriteLine($"{firstValue.Length}");
var secondValue = firstValue;
firstValue = "Welcome";
//No warning
Console.WriteLine($"{firstValue.Length}");
When the program prints the firstValue, it determines that firstValue could become null and throws a warning.
However, for the second print statement, it doesn’t throw any warning as it is not null.
Variable annotations you can use to declare variables explicitly as null.
State whether a variable is a nullable or a non-nullable reference type. You can declare a variable as a nullable reference type by appending “?” to its type. For instance, the following variable is a nullable string variable.
string? Value;
Attributes in APIs that enable flow analysis to decide the null state of a variable
You can apply attributes to parameters in method signatures to indicate they can become null. The following example shows the ‘value’ parameters’ null state using the attribute’ NotNullWhen.’ These attributes allow developers to understand the API semantics to handle the caller code accordingly.
public static bool IsNullValue([NotNullWhen(false)] string value);
#2 Eliminate returning null values with the Null Object pattern
Null Object pattern is another great technique to avoid returning nulls wherever possible. It is a special case of State and Strategy Pattern. In NullObject Pattern, a null object is an object of the class that defines do nothing behavior. Instead of a null, the programmer can use it in places that need a null check and return a non-functional object to avoid NullReferenceExceptions.
The following diagram shows an example design of a NullObject pattern.
NullProduct is the null object class that does not contain any functionality or does nothing. Clients can use this class object at places where it wants to return null values.
#3 Use Result type as the return type
While the resolutions mentioned above are for returning more meaningful values rather than null ones, how about using a new return type to handler errors instead of throwing exceptions? The Result type exactly serves that purpose.
A lot of programming languages enable the use of returning Result type in methods. For example, in C#, FluentResults is a lightweight library that allows you to declare success or failure without throwing exceptions. You can use the Result for methods with return types and void methods that do not have a return type.
public Result<Product> GetProduct()
{
if (this.State == ProductState.Deleted)
return Result.Fail<Product>("The product is deleted");
return Result.Ok(task);
}
The method Fail of Result object indicates that the return value doesn’t return an object. Once the method callers get the Result object, processing should be done to check the success or failure of the operation using the Value and ValueOrDefault properties of the Result object.
Other similar libraries are:
What are the pros and cons of returning null values from a method?
As discussed throughout this article, returning null values has more disadvantages than advantages.
The major disadvantage is that it creates more work for the method callers. If the developer forgets to handle the null value, exceptions occur, causing the programs to terminate. Especially if callers are not informed to handle it explicitly in their code, they can access null object methods.
In larger projects, there is a possibility to propagate this further down the code and cause programs to fail unknowingly to the developers.
Null is usable only if the method’s purpose is to indicate there is no data. Other than that, eliminate the use of null values as much as possible.
Conclusion
Returning null values from a method is often a simple mistake many programmers make. However, that simple mistake could become a million-dollar mistake if you do not handle it properly. Otherwise, the code throws exceptions.
Several alternatives for returning null values include using null object reference types, a null object pattern, and a result type as the return type. Therefore, the recommendation is to return an empty value instead of a null to keep the code clean and error-free.