Nulls, nulls everywhere!
Two weeks ago, I published an article about typing variables, properties and parameters properly in TypeScript, to avoid null reference errors (or their equivalent) as much as possible. One way I suggested you strengthen your TypeScript app was to activate some TypeScript compiler options, such as the “strictPropertyInitialization” option. But if you did so in an existing code base, you probably realized that you had a “ton” of new errors the next time you compiled your code.
In the Changent.io products we develop, here at BesLogic, once we turned this compiler option on, we had, literally, a thousand errors appear in our console. A valiant colleague processed each new error, typed the properties correctly, and went on to fix hundreds of potential null reference errors that had been secretly hiding from the TypeScript compiler in our Angular templates and in our components.
But… that’s a _lot_ of nulls. And now, we have to have null checks everywhere! Is there a better way we could do things, in order to avoid so much defensive programming everywhere in our code?
That’s what I’ll try to answer in today’s article, where I will explore a technique of organizing your code to separate null-dealing code from your core logic code. This time, I will not target TypeScript specifically, but programming languages in general.
Languages such as TypeScript or F# are better candidates, because you have to explicitly type your data as being nullable (or being optional) for them to be so, which means that you have more type guarantees at compile time that your code is safe. However, it can still work with languages like C#. At the very least, it should work perfectly with C#’s nullables (and, who knows, maybe with everything, when C# 8 is out!).
How to avoid nulls in the first place
In languages such as F# or TypeScript, it’s really easy to avoid nulls at all: don’t create types that can be null!
Let’s say you have a function that does something to a Person type. If your function does not know how to handle a null Person, you simply have to say that your function only accepts a Person.
This kind of architecture is quite common. In this case, my suggestion is adapted from the IO workflows often presented on Scott Wlaschin’s excellent website, fsharpforfunandprofit.com. I found that the pattern on keeping “problematic” code or data at the topmost, the edges, the “limits” of your application could be easily applied to null data as well, to keep a cleaner code.
In Angular code, for instance, you can define your data handling logic in services, and do none in your components. Your components would be responsible of obtaining the correct data and doing null checks, but in your service’s methods, you would not have to worry about null values, as you would have avoided them altogethers in your method singatures.
Layers: null on top, non-null underneath
Nulls, undefined and options are very useful to represent incorrect data. For instance, in our example above, the “fetchPerson” function can potentially return “undefined”. In this case, it represented the fact that the person is actually coming from elsewhere, and that something, anything, could go wrong in the process: you could have provided an invalid ID, there could have been a temporary network outage, etc.
It’s up to you to decide how to deal with such scenarios when they happen. If you chose to use nulls, you then have a very specific meaning for your null value: it is an “invalid” value. You should then most likely avoid processing it at all, in such cases.
On the other hand, nulls can be used to represent data that is optional. In those cases, you will actually want your core business code to have access to those nullable values. But then, you’ll know it’s not a painful problem, because you will often do something with the null value, instead of just returning from your process.
In F#, you can use the “Option” type for the second meaning, and the “Result” type for errors and such. However, these are not built-in types in C# and TypeScript in the cases first mentionned.
That’s why separating your logic from these values can be a good way to reduce the impact of having so much nulls in your code!
That’s all for this week! Thank you for coming by!