Coding Style – Are Nullable Types Preferable to Magic Numbers?

coding-stylenull

I have been having a little bit of a debate with a coworker lately. We are specifically using C#, but this could apply to any language with nullable types. Say for example you have a value that represents a maximum. However, this maximum value is optional. I argue that a nullable number would be preferable. My coworker favors the use of zero, citing precedent. Granted, things like network sockets have often used zero to represent an unlimited timeout. If I were to write code dealing with sockets today, I would personally use a nullable value, since I feel it would better represent the fact that there is NO timeout.

Which representation is better? Both require a condition checking for the value meaning "none", but I believe that a nullable type conveys the intent a little bit better.

Best Answer

Consider:

  • Language,

  • Framework,

  • Context.

1. Language

Using ∞ can be a solution for a maximum.

  • JavaScript, for example, has an infinity. C# doesn't¹.

  • Ada, for example, has ranges. C# doesn't.

In C#, there is int.MaxValue, but you cannot use it in your case. int.MaxValue is the maximum integer, 2,147,483,647. If in your code, you have a maximum value of something, like a maximum accepted pressure before something explodes, using 2,147,483,647 has no sense.

2. Framework

.NET Framework is rather inconsistent on this point, and its usage of magic values can be criticized.

For example, "Hello".IndexOf("Z") returns a magic value -1. It maybe makes it easier (does it?) to manipulate the result:

int position = "Hello".IndexOf("Z");
if (position > 0)
{
    DoSomething(position);
}

rather than using a custom structure:

SearchOccurrence occurrence = "Hello".IndexOf("Z");
if (occurrence.IsFound)
{
    DoSomething(occurrence.StartOffset);
}

but is not intuitive at all. Why -1 and not -123? A beginner may also mistakenly think that 0 means "Not found" too or just mistype (position >= 0).

3. Context

If your code is related to timeouts in network sockets, using something which was used by everyone for decades for the sake of being consistent is not a bad idea. Especially, 0 for a timeout is very clear: it's a value which cannot be zero. Using a custom class in this case may make things more difficult to understand:

class Timeout
{
    // A value indicating whether there is a timeout.
    public bool IsTimeoutEnabled { get; set; }

    // The duration of the timeout, in milliseconds.
    public int Duration { get; set; }
}
  • Can I set Duration to 0 if IsTimeoutEnabled is true?
  • If IsTimeoutEnabled is false, what happens if I set Duration to 100?

This can lead to multiple mistakes. Imagine the following piece of code:

this.currentOperation.Timeout = new Timeout
{
    // Set the timeout to 200 ms.; we don't want this operation to be longer than that.
    Duration = 200,
};

this.currentOperation.Run();

The operation runs for ten seconds. Can you see what's wrong with this code, without reading the documentation of Timeout class?

Conclusion

  • null expresses well the idea that the value is not here. It's not provided. Not available. It's neither a number, nor a zero/empty string or whatsoever. Don't use it for maximum or minimum values.

  • int.MaxValue is strongly related to the language itself. Don't use int.MaxValue for a maximum speed limit of Vehicle class or a maximum acceptable speed for an aircraft, etc.

  • Avoid magic values like -1 in your code. They are misleading and lead to mistakes in code.

  • Create your own class which would be more straightforward, with the minimum/maximum values specified. For example VehicleSpeed can have VehicleSpeed.MaxValue.

  • Don't follow any previous guideline and use magic values if it's a general convention for decades in a very specific field, used by most people writing code in this field.

  • Don't forget to mix approaches. For example:

    class DnsQuery
    {
        public const int NoTimeout = 0;
    
        public int Timeout { get; set; }
    }
    
    this.query.Timeout = 0; // For people who are familiar with timeouts set to zero.
    // or
    this.query.Timeout = DnsQuery.NoTimeout; // For other people.
    

¹ You can create your own type which includes infinity. Here, I'm talking about native int type only.