Поиск  
Always will be ready notify the world about expectations as easy as possible: job change page
Jul 11, 2021

Never return NULL references from your functions

Never return NULL references from your functions
Автор:
Sasha Mathews
Источник:
Просмотров:
3006

In C#, reference types can be assigned null values. This is something that developers will have to live with until they use classes to create software. Unfortunately, the folks at Microsoft cannot simply disallow null assignment to reference variables at compile time, because that would break the codebase of almost every project.

Returning Null is bad practice

What is actually wrong with returning a null reference from the method?

Let’s take a look at this simple method:

public Order GetOrder(int orderId)
{
    var order = _db.Orders.FirstOrDefault(u => u.OrderId == orderId);
    return order;
}

The FirstOrDefault method silently returns null if no order is found in the database.

There are a couple of problems here:

  • Callers of GetOrder method must implement null reference checking to avoid getting a NullReferenceException when accessing Order class members. But how is the caller supposed to know how to properly implement error handling? Should the caller throw an exception if it receives a null reference or return some error code?
  • When each caller implements the validation logic on its own, it will be duplicated and the chances are high that the logic will not be consistent across all callers. This is a violation of DRY principle.
  • Getting a nullvalue is an ambiguous for caller, because it doesn’t say whether the null is returned due to the bug or due to the fact that the order was not found in the database. Returning null is definitely not a domain-driven design approach.
  • Returning null is often a violation of the fail fast programming principle. The null can appear due to some issue in the application. The issue can even go to production if the developer has not implemented proper exception handling, which can help quickly detect the issue.

Developers can do several things to improve the situation, such as using a nullable reference types, throwing an exception, or using the null object design pattern.

Use Nullable reference types

If the developer really needs to return a null value from the method (in 99% of cases this is not necessary), the return type can be marked as a nullable reference type (the feature was introduced in C# 8.0).

public Order? GetOrder(int orderId)
{
    var order = _db.Orders.FirstOrDefault(u => u.OrderId == orderId);
    return order;
}

Marking a return type as nullable has two main benefits:

  • The method signature clearly states that it can return a null reference. This way, callers know for sure that they need to implement error handling just by looking at the method signature.
  • The compiler will emit a warning if the caller accesses members of the Order class without first checking for a null reference.

Fail fast by throwing an Exception

The fail fast principle is an important principle in software engineering. The idea is pretty simple — as soon as the application detects a problem (null reference), it should throw an exception.

Incorrect orderId can be caused by the following reasons:

  • The auto mapping library is not configured properly to map orderId values.
  • Some code accidentally assigned the wrong value to orderId variable before calling GetOrder method.
  • The frontend does not pass order IDs to the backend.

Each reason described is a bug in the application. Thus, according to the fail fast principle, as soon as the system detects that the orderId is not valid, it should throw an exception.

The easiest way to throw an exception for orders not found is to use the First method instead of FirstOrDefault in the implementation.

public Order GetOrder(int orderId)
{
    var order = _db.Orders.First(u => u.OrderId == orderId);
    return order;
}

But we have another problem: callers will get InvalidOperationException without any valuable information because this is a common .NET exception. The solution for that is to introduce a custom exception type which the developer should throw once the order was not found in the database.

public Order GetOrder(int orderId)
{
    var order = _db.Orders.FirstOrDefault(u => u.OrderId == orderId);
    
    if (order == null)
    {
        throw new OrderNotFoundException($"Order {orderId} was not found in the database.");
    }
    
    return order;
}

To summarize the benefits of the custom exceptions approach:

  • The chances of issues being discovered and fixed prior to deploying a production environment are much higher.
  • The caller will get detailed information about the problem, which will simplify the troubleshooting process.

Use Null object design pattern

Another way to avoid returning null is to use a Null object design pattern.

A null object is an object without behavior like a stub that a developer can return to the caller instead of returning null value.

public Order GetOrder(int orderId)
{
    var order = _db.Orders.FirstOrDefault(u => u.OrderId == orderId);
    
    if (order == null)
    {
        return new Order();
    }
    
    return order;
}

The caller doesn’t need to check the order for null value because a real but empty Order object is returned.

The null object pattern should be carefully considered as an alternative to throwing an exception. Using the pattern is contrary to the fail fast principle.

This pattern should be used only when callers would otherwise check the object for null in order to skip access to its members.

Conclusion

The conclusion is simple — never return null references from your methods.

Похожее
Jun 3
Author: Dayanand Thombare
Introduction Delegates are a fundamental concept in C# that allow you to treat methods as objects. They provide a way to define a type that represents a reference to a method, enabling you to encapsulate and pass around methods as...
Nov 19, 2020
Author: Ryszard Seniuta
Although SQL Server's Full-Text search is good for searching text that is within a database, there are better ways of implementing search if the text is less-well structured, or comes from a wide variety of sources or formats. Ryszard takes...
Jul 23, 2023
Unlocking Resilience and Transient-fault-handling in your C# Code In an ideal world, every operation we execute, every API we call, and every database we query, would always work flawlessly. Unfortunately, we live in a world where network outages, server overloads,...
Feb 1
Author: Sohail Aslam
Introduction Welcome to the world of C#! Whether you’re a seasoned developer or just starting your programming journey, the power and versatility of C# can elevate your coding experience. In this article, we’ll explore a curated collection of tips and...
Написать сообщение
Тип
Почта
Имя
*Сообщение