• Why null is bad
  • Yegor Bugayenko
  • Personal Interest (non-nuggets translation Project)
  • Translator: The ape in Gao Lao Zhuang village
  • Proofreader: None, first translation, please correct in the comments

Let’s start with a simple example of using NULL as a return value in Java:

public Employee getByName(String name) {
  int id = database.find(name);
  if (id == 0) {
    return null;
  }
  return new Employee(id);
}
Copy the code

The biggest problem with this method is that it returns NULL instead of an object. Using NULL in object-oriented specifications is a very bad practice and should be avoided at all costs. There are many arguments to support this view, including Tony Hoare’s talk Null References, The Billion Dollar Mistake and David West’s Book Object Thinking. Next, I’ll do a little bit of wrapping up all the arguments and use an appropriate object-oriented structure instead of NULL as the return value. For now, there are at least two alternatives to using NULL.

1, use,Empty object design pattern(It is best to define a constant)

public Employee getByName(String name) {
  int id = database.find(name);
  if (id == 0) {
    return Employee.NOBODY;
  }
  return Employee(id);
}
Copy the code

When an object cannot be returned, an exception can be thrown to cause the caller to fail-fast.

public Employee getByName(String name) {
  int id = database.find(name);
  if (id == 0) {
    throw new EmployeeNotFoundException(name);
  }
  return Employee(id);
}
Copy the code

Now take a look at the arguments against null. In addition to Tony Hoares’ lecture and David West’s book mentioned above, I’ve also seen Clean Code by Robert Martin, Code Complete by Steve McConnell, Say “No” to “Null” by John Sonmez, and StackOverflow Is Returning Null Bad Design? .

Special error handling

Every time you take an object reference as input you must check whether it is null or valid. Forgetting to check will result in a null Pointer Exception (NPE) at runtime. Your code logic will be contaminated with multiple checks and if/ THEN /else branches. Consider the following example:

// Bad design, do not reuse
Employee employee = dept.getByName("Jeffrey");
if (employee == null) {
  System.out.println("can't find an employee");
  System.exit(-1);
} else {
  employee.transferTo(dept2);
}
Copy the code

This is how C and many other procedural programming languages handle exceptions. Object-oriented programming introduced exception handling to eliminate these special error-handling logic. In object-oriented programming, we bubble exceptions up and down to the application layer, so our code gets smaller and prettier.

dept.getByName("Jeffrey").transferTo(dept2);
Copy the code

Null is a feudal relic of procedural programming. Use null objects or exceptions instead.

Semantic ambiguity

In order to show the “function will return to the real object or null” express the meanings, getByName () should be named getByNameOrNullIfNotFound (). Every similar function should do this, otherwise it will confuse the code reader.

For semantic accuracy, it is worth defining longer names for functions.

To disambiguate, the function tries to return a real object, a null object, or throw an exception.

Some would argue that sometimes null has to be returned for performance. For example, the get() method in the Java Map interface returns null if the corresponding entry is not found in the Map, for example:

Employee employee = employees.get("Jeffrey");
if (employee == null) {
  throw new EmployeeNotFoundException();
}
return employee;
Copy the code

Since the Map’s get() method returns NULL, the code above searches the Map only once. If we wanted to override the Map’s get() method to throw an exception if the item could not be found, the code would look like this:

if(! employees.containsKey("Jeffrey")) { // first search
  throw new EmployeeNotFoundException();
}
return employees.get("Jeffrey"); // second search
Copy the code

Obviously, this method is twice as slow as the first method, so what do we do? I think the Map interface design is flawed (no offense to the author), it should return an Iterator to allow our code to look like this:

Iterator found = Map.search("Jeffrey");
if(! found.hasNext()) {throw new EmployeeNotFoundException();
}
return found.next();
Copy the code

BTW, this is the idea behind the map::find() method in the C++ standard library (STL).

Computer thinking vs. object thinking

If someone knows that a Java object is a pointer to a data structure and that NULL is a null pointer (equal to 0x00000000 on Intel x86 processors), they should be able to accept the if(Employee == null) statement. However, if you think in terms of objects, this statement makes no sense. From an object’s point of view, our code looks like this:

  • Hello, is this the software department?
  • Yes.
  • May I speak with Your Employee, Jeffrey?
  • Please wait a moment…
  • Hello
  • Are you a NULL?

The last sentence of the conversation above looks strange, doesn’t it? Conversely, if they had just hung up on me after I wanted to speak to Jeffrey, it would have created a quick glitch for us. At this point, we can try to call back or directly tell our supervisor that Jeffrey cannot be reached to complete a larger deal.

Alternatively, they can have us talk to another person who is not Jeffrey, but who can help us with most problems if we need a “specific” Jeffrey (null object) or refuse to help.

Slow Failing

As opposed to failing fast, the code above attempts to die slowly and kill others. It hides the failure from the caller rather than letting it know that something is wrong and needs exception handling right away. This conclusion is similar to the discussion in the “special error handling” section above. It’s best to make your code as fragile as possible and break it if necessary.

Make sure that your method is extremely demanding on the operands provided by the caller. Throw an exception if the data provided by the caller is insufficient or does not fit the main usage scenario of the method. Or return a NULL object that exposes some common behavior and throws exceptions for all other calls, as follows:

public Employee getByName(String name) {
  int id = database.find(name);
  Employee employee;
  if (id == 0) {
    employee = new Employee() {
      @Override
      public String name(a) {
        return "anonymous";
      }
      @Override
      public void transferTo(Department dept) {
        throw new AnonymousEmployeeException(
          "I can't be transferred, I'm anonymous"); }}; }else {
    employee = Employee(id);
  }
  return employee;
}
Copy the code

Mutable and incomplete objects

In general, it is strongly recommended that you design objects with immutability in mind. This means that the object gets everything necessary during instantiation and never changes its state throughout its life cycle. Null is often used in lazy loading to make objects incomplete and mutable. Such as:

public class Department {
  private Employee found = null;
  public synchronized Employee manager(a) {
    if (this.found == null) {
      this.found = new Employee("Jeffrey");
    }
    return this.found; }}Copy the code

This technique, though widely used, is an anti-design pattern in object-oriented programming. This is mainly because it makes an Employee object responsible for computing platform performance issues, which should be transparent to the Employee object.

Rather than managing state and exposing business-related behavior, let objects handle caching of their own results-that’s the purpose of lazy loading. Caching is not something Employee should do in the office, is it?

The solution is not to use lazy loading in this crude way, as in the example above. Instead, move the caching problem to another layer of the application. In Java, for example, you can use section-oriented programming techniques. For example, jCABi-Aspects uses the @cacheable annotation to cache values returned by methods:

import com.jcabi.aspects.Cacheable;
public class Department {
  @Cacheable(forever = true)
  public Employee manager(a) {
    return new Employee("Jacky Brown"); }}Copy the code

Hopefully, this article will help you stop using NULL as a return value in your code.