40

For example, suppose I have a class, Member, which has a lastChangePasswordTime:

class Member{
  .
  .
  .
  constructor(){
    this.lastChangePasswordTime=null,
  }
}

whose lastChangePasswordTime can be meaningful absent, because some members may never change their passwords.

But according to If nulls are evil, what should be used when a value can be meaningfully absent? and https://softwareengineering.stackexchange.com/a/12836/248528, I shouldn't use null to represent a meaningfully absent value. So I try to add a Boolean flag:

class Member{
  .
  .
  .
  constructor(){
    this.isPasswordChanged=false,
    this.lastChangePasswordTime=null,
  }
}

But I think it is quite obsolete because:

  1. When isPasswordChanged is false, lastChangePasswordTime must be null, and checking lastChangePasswordTime==null is almost identical to checking isPasswordChanged is false, so I prefer check lastChangePasswordTime==null directly

  2. When changing the logic here, I may forget to update both fields.

Note: when a user changes passwords, I would record the time like this:

this.lastChangePasswordTime=Date.now();

Is the additional Boolean field better than a null reference here?

ocomfd
  • 5,750

8 Answers8

72

I don't see why, if you have a meaningfully absent value, null should not be used if you are deliberate and careful about it.

If your goal is to surround the nullable value to prevent accidentally referencing it, I would suggest creating the isPasswordChanged value as a function or property that returns the result of a null check, for example:

class Member {
    DateTime lastChangePasswordTime = null;
    bool isPasswordChanged() { return lastChangePasswordTime != null; }

}

In my opinion, doing it this way:

  • Gives better code readability than a null-check would, which might lose context.
  • Removes the need for you having to actually worry about maintaining the isPasswordChanged value that you mention.

The way that you persist the data (presumably in a database) would be responsible for ensuring that the nulls are preserved.

TZHX
  • 5,072
65

nulls aren't evil. Using them without thinking is. This is a case where null is exactly the correct answer - there is no date.

Note that your solution creates more problems. What is the meaning of the date being set to something, but the isPasswordChanged is false? You've just created a case of conflicting information that you need to catch and treat specially, while a null value has a clearly defined, unambiguous meaning and cannot be in conflict to other information.

So no, your solution isn't better. Allowing for a null value is the right approach here. People who claim that null is always evil no matter the context don't understand why null exists.

Tom
  • 766
29

Depending on your programming language, there might be good alternatives, such as an optional data type. In C++(17), this would be std::optional. It can either be absent or have an arbitrary value of the underlying data type.

dasmy
  • 473
11

Correctly using null

There are different ways of using null. The most common and semantically correct way is to use it when you may or may not have a single value. In this case a value either equals null or is something meaningful like a record from database or something.

In these situations you then mostly use it like this (in pseudo-code):

if (value is null) {
  doSomethingAboutIt();
  return;
}

doSomethingUseful(value);

Problem

And it has a very big problem. The problem is that by the time you invoke doSomethingUseful the value may not have been checked for null! If it wasn't then the program will likely crash. And the user may not even see any good error messages, being left with something like "horrible error: wanted value but got null!" (after update: although there may be even less informative error like Segmentation fault. Core dumped., or worse yet, no error and incorrect manipulation on null in some cases)

Forgetting to write checks for null and handling null situations is an extremely common bug. This is why Tony Hoare who invented null said at a software conference called QCon London in 2009 that he made the billion-dollar mistake in 1965: https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

Avoiding the problem

Some technologies and languages make checking for null impossible to forget in different ways, reducing the amount of bugs.

For example Haskell has the Maybe monad instead of nulls. Suppose that DatabaseRecord is a user-defined type. In Haskell a value of type Maybe DatabaseRecord can be equal Just <somevalue> or it may be equal to Nothing. You can then use it in different ways but no matter how you use it you cannot apply some operation on Nothing without knowing it.

For instance this function called zeroAsDefault returns x for Just x and 0 for Nothing:

zeroAsDefault :: Maybe Int -> Int
zeroAsDefault mx = case mx of
    Nothing -> 0
    Just x -> x

Christian Hackl says C++17 and Scala have their own ways. So you may want to try to find out if your language has anything like that and use it.

Nulls are still in wide use

If you don't have anything better then using null is fine. Just keep watching out for it. Type declarations in functions will help you somewhat anyway.

Also that may sound not very progressive but you should check if your colleagues want to use null or something else. They may be conservative and may not want to use new data structures for some reasons. For instance supporting older versions of a language. Such things should be declared in project's coding standards and properly discussed with the team.

On your proposal

You suggest using a separate boolean field. But you have to check it anyway and still may forget to check it. So there is nothing won here. If you can even forget something else, like updating both values each time, then it's even worse. If the problem of forgetting to check for null is not solved then there is no point. Avoiding null is difficult and you should not do it in such a way that makes it worse.

How not to use null

Finally there are common ways to use null incorrectly. One such way is to use it in place of empty data structures such as arrays and strings. An empty array is a proper array like any other! It is almost always important and useful for data structures, that can fit multiple values, to be able to be empty, i.e. has 0 length.

From algebra standpoint an empty string for strings is much like 0 for numbers, i.e. identity:

a+0=a
concat(str, '')=str

Empty string enables strings in general to become a monoid: https://en.wikipedia.org/wiki/Monoid If you don't get it it's not that important for you.

Now let's see why it's important for programming with this example:

for (element in array) {
  doSomething(element);
}

If we pass an empty array in here the code will work fine. It will just do nothing. However if we pass a null here then we will likely get a crash with an error like "can't loop through null, sorry". We could wrap it in if but that's less clean and again, you might forget to check it

How to handle null

What doSomethingAboutIt() should be doing and especially whether it should throw an exception is another complicated issue. In short it depends on whether null was an acceptable input value for a given task and what is expected in response. Exceptions are for events that were not expected. I will not go further into that topic. This answer very is long already.

Gherman
  • 945
4

In addition to all of the very good answers previously given, I'd add that every time you're tempted to let a field be null, think carefully if it might be a list type. A nullable type is equivalently a list of 0 or 1 elements, and often this can be generalized to a list of N elements. Specifically in this case, you might want to consider lastChangePasswordTime being a list of passwordChangeTimes.

Joel
  • 211
  • 1
  • 7
2

Ask yourself this: which behavior requires the field lastChangePasswordTime?

If you need that field for a method IsPasswordExpired() to determine if a Member should be prompted to change their password every so often, I would set the field to the time the Member was initially created. The IsPasswordExpired() implementation is the same for new and existing members.

class Member{
   private DateTime lastChangePasswordTime;

   public Member(DateTime lastChangePasswordTime) {
      // set value, maybe check for null
   }

   public bool IsPasswordExpired() {
      DateTime limit = DateTime.Now.AddMonths(-3);
      return lastChangePasswordTime < limit;
   }
}

If you have a separate requirement that newly created members have to update their password, I would add a separated boolean field called passwordShouldBeChanged and set it to true upon creation. I would then change the functionality of the IsPasswordExpired() method to include a check for that field (and rename the method to ShouldChangePassword).

class Member{
   private DateTime lastChangePasswordTime;
   private bool passwordShouldBeChanged;

   public Member(DateTime lastChangePasswordTime, bool passwordShouldBeChanged) {
      // set values, maybe check for nulls
   }

   public bool ShouldChangePassword() {
      return PasswordExpired(lastChangePasswordTime) || passwordShouldBeChanged;
   }

   private static bool PasswordExpired(DateTime lastChangePasswordTime) {
      DateTime limit = DateTime.Now.AddMonths(-3);
      return lastChangePasswordTime < limit;
   }
}

Make your intentions explicit in code.

Rik D
  • 4,975
2

First, nulls being evil is dogma and as usual with dogma it works best as a guideline and not as pass/no pass test.

Secondly, you can redefine your situation in a way that it makes sense that the value can never be null. InititialPasswordChanged is a Boolean initially set to false, PasswordSetTime is the date and time when the current password was set.

Note that while this does come at a slight cost, you can now ALWAYS calculate how long it has been since a password was last set.

jmoreno
  • 11,238
1

Both are 'safe/sane/correct' if the caller checks before use. The issue is what happens if the caller doesn't check. Which is better, some flavour of null error or using an invalid value?

There is no single correct answer. It depends on what you are worried about.

If crashes are really bad but the answer isn't critical or has an accepted default value, then perhaps using a boolean as a flag is better. If using the wrong answer is a worse issue than crashing, then using null is better.

For the majority of the 'typical' cases, fast failure and forcing the callers to check is the fastest way to sane code, and hence I think null should be the default choice. I wouldn't put too much faith in the "X is the root of all evil" evangelists though; they normally haven't anticipated all the use cases.

ANone
  • 311