Use TimeSpan Or Specify Units In Duration Properties And Parameters

A special case of primitive obsession is the use of an int value to describe a span of time. You see this all the time in various APIs, and it’s a frequent source of bugs and confusion. Developers are forced to either guess or try to find the documentation that describes what units int timeout or int delay actually uses.

Having a retry delay of 1 millisecond instead of 1 second, for example, is a huge difference - that’s 1000x more load on your system! But so is having a timeout of 1000 seconds (nearly 17 minutes!) instead of 1000 milliseconds (1 second). Either way, it’s three orders of magnitude away from the expected and intended behavior, which can lead to production incidents and difficult-to-diagnose bugs.

The .NET framework has gotten better about this over time. See:

1
2
3
4
// good examples ✅
public static System.Threading.Tasks.Task Delay(int millisecondsDelay);
public bool Wait(int millisecondsTimeout);
public static void Sleep(int millisecondsTimeout);

However, not all code is so diligent. A quick search of public code using sourcegraph finds 1000s of cases of int timeout being used with no indication of the actual units (seconds, minutes, milliseconds, etc.). And more of the same for int delay .

Some bad examples:

1
2
3
4
5
6
7
// bad examples ❌

// src/BootstrapBlazor/Components/Popover/PopupOptionBase.cs
public int Delay { get; set; } = 4000;

// Libraries/src/Amazon.Lambda.Annotations/LambdaFunctionAttribute.cs
public uint Timeout { get; set; }

This is an example of the principle of make the implicit explicit. Don’t make developers guess what units they should be using!

Fixing Missing Units in Timeout and Delay Parameters and Properties

There are two simple ways to avoid these problems.

The first is, put the units into the name everywhere the type is used. See the good examples above. Prefer delayMilliseconds to delay. Prefer timeoutSeconds to timeout. Make it obvious to the developer what the units are.

The second is, avoid using the wrong type in the first place. Using an int to describe a value that should typically be a positive value within a fairly small known range (probably less than 1000 for seconds or minutes; probably less than 100,000 for milliseconds) is just bad design. Using a uint, as the AWS example above does, is slightly better since it at least ensures the value cannot be negative. But why not use the built-in TimeSpan type for these values? In all but the most performance-critical scenarios, the TimeSpan struct is a better choice. Internally it stores its value in a long, so it does use more memory than an int, but the expressiveness of the type is far better.

Here’s what the bad examples would look like when properly using TimeSpan:

1
2
3
4
// excellent examples 🌟

public TimeSpan Delay { get; set; } = TimeSpan.FromSeconds(4);
public TimeSpan Timeout { get; set; }

With TimeSpan, the units are crystal clear at the point of assignment (TimeSpan.FromSeconds(4)) and the compiler ensures type safety throughout your codebase.

Note that by default a TimeSpan can represent negative values. To be even more restrictive you would want to use a custom value object. Another benefit of using better types to represent values is that you don’t need to have as much guard/validation logic in your methods that use these values. If you know your custom type is never negative, you don’t have to check for that in your code. You can use the approach of Parse, Don’t Validate (video) to parse untyped user input into your stronger more expressive types so that you’re not having to check primitive values at multiple places in your call stack. By using more explicit types and ensuring they are valid when constructed (and are immutable), your design improves because you make it impossible for it to represent invalid states.

Summary

Using int types to represent spans of time for things like delay or timeout is a frequent source of confusion and bugs in software applications. Always be explicit about the units of time being represented. Two simple ways to achieve this are to put the units in the name of the property or parameter or to use a type that is designed to represent a span of time. Ideally, you’ll want to avoid too much primitive obsession in your code and use explicit types that make it impossible to represent invalid states.