Narrowing types to avoid primitive obsession
Published by marco on
Recently, I saw that the following error had been fixed in a code review.
Classic primitive obsession error
The error shown above is an example of a design smell called Primitive Obsession. This is where code is “obsessed” with primitives, in that it uses a much “wider” type than is actually acceptable.
Whereas C++ has a typedef
, TypeScript and Delphi Pascal have a type
, C# has … nothing simple. The linked article describes a hand-coded version for making “narrower” types (e.g., MeanLength
or ShortFiber
). Our go-to generated-source guru Andrew Lock describes a solution that uses the StronglyTypedId package, but also links to a series from 2020 by Thomas Levesque that uses records.
It looks like you can use something like public record MeanLength(int Value);
to succinctly define a narrower type. While it’s nice that C# autogenerates all the necessary machinery (equals, hashCode, etc.) for the record, it’s also unfortunate that it’s necessary, as we’re usually just trying to disambiguate two ints
without further validation or restriction.
Also, I’m not recommending you leverage the type system to avoid primitive confusion in every code base! I’m just noting that the error that arose is so common that it not only has a name, but that there are well-defined solutions for avoiding that class of problems using the type system.
Before you start messing with these types in a language like C#—where, as noted, you don’t have a simple type-disambiguation mechanism—you need to have everyone in the team on board. Many programmers will consider them to be too heavy-handed (they suspect it affects performance somehow, and aren’t willing to trade any potential and unproven performance drawback anywhere for increased type-safety). Those are usually the same programmers who write code with a ton of primitive obsession and zero automated tests, so take their critique for what it’s worth.