Good Code Is Precise
The space of programs that execute but behave differently to how one expects is unfortunately very large. These differences in behaviour can be very subtle, and often lead to bugs.
Good code is therefore precise. It expresses itself as explicitly and specifically as possible with respect to the underlying primitives provided by the language (data types, operators etc).
An example
Consider the use of truthy/falsy checks.
Say we have a count variable in Javascript:
// a truthy check on count
if (count) {
// do a something
}
An average reader would likely expect the if block to run if count is a positive number.
This is what the code actually does:
if (
count !== undefined &&
count !== null &&
count !== 0 &&
count !== -0 &&
count !== false &&
count !== '' &&
count !== NaN &&
count !== BigInt(0) &&
count !== document.all
) {
// do a something
}
đ
 I doubt many experienced JS engineers would know all these falsy values (I personally didnât know about BigInt(0) and document.all until just then). But this is what the seemingly trivial truthy comparison is actually doing.
The problem is that the truthy comparison with count is vague with respect to:
- What data type to compare to.
- What value of the data type to compare to.
Wrt 1., dealing with null types explicitly is very important because often we want to discontinue execution of the current logic because it doesnât make sense to continue without a valid value. Javascript exacerbates this by having 2 main null types (undefined and null) đ« .
Ok so letâs make the generous assumption that we are only interested in the comparison to a number.
Do we want the if block to run if count = 0? Itâs unclear. Maybe the conditionalâs body will provide a clue, but we really shouldnât have to dig into the body of a conditional code block to divine the specification of its condition.
Some other questions that the code doesnât clearly answer include:
- What should happen if
countis negative? - What should happen if
countisnull/undefined?
We can make the following observation:
Bad code raises more questions than it answers.
Ok, letâs try being more precise in both the data type and the value we want to compare to:
if (count > 0) {
// do a something
}
This is a bit better because itâs now clear that the block should execute when count is a positive number.
But because JS is dynamically typed, count could really be any of the 8 data types. And we really donât want the reader needing to know how comparison operators work between all those types and the number 0 (e.g. '1' > 0 is true, whilst null > 0 is false)
Ok, letâs try adding Typescript for compile-time type checking:
let count: number
// ...some logic
if (count > 0) {
// do a something
}
Great! We now know count will be a number, and it should be greater than 0 for our conditional block to run.
Our code is more precise, the intention is clear, and we shouldnât be as worried about running into bugs đ.
Of course, compile-time checking is not fool-proof. At run-time, the variable could change if we assign count to the result of an API call with an any return type:
function getValueFromAPI(): any {
return true
}
let count: number = getValueFromAPI()
This is obviously a bit of a contrived example using the any type override, but it just serves to show that things can still break even if it seems they canât.
The Price of Precision
There are also tradeoffs with increasing the precision of our code. In the above cases, they include:
- Increased verbosity
- Overhead of type checking
Wrt 1., I doubt many would argue going from if (count) â if (count > 0) is overly verbose given the large gain in precision.
Wrt 2., this would be more contentious as introducing static types and maintaining a type checker is more work. Factors to consider include the expected scale of the application, how many engineers will work on it and their familiarity level with TS. An even more costly choice would be to rewrite code from a dynamically typed to statically typed language such as Go.
There are many other factors that impact the precision of our code:
- Naming choices
- Cohesion of our modules
- Choice of programming language
I may try to address these in a future post đ.
Closing Thoughts
All of this is not to say that dynamically typed languages are terrible (đ¶ïž), nor should we never use truthy/falsy checks.
How much precision we lose using truthy/falsy checks is language dependent, and conventions across languages differ and matter (e.g. Python code often uses truthy expressions for non-boolean variables e.g. if my_list:).
But generally, the precision of code matters and should be a primary factor in considering its quality.
Good code is as precise as possible, with mindful consideration to the tradeoffs.
Thanks to Yifan for feedback and discussion.