How to Refactor a Null Checking Condition With Object Composition in TypeScript
Using the Introduce Special Case pattern to refactor a null checking condition
Early in my career, the most commonly occurring condition in TypeScript and Angular was the null
checking condition. I think other developers have the same habit of making their code more stable. Such a condition isn’t bad, but when we check the same special value every time in different places in our code, we may have redundant code and we may not be complying with the DRY principle.
A null
checking condition is nothing more than a check for data availability:
It is helpful to implement a Special Case object to handle such conditions when most or all of them have the same reaction to the particular value. We could separate that reaction to a single place.
In TypeScript, there are two different innate null
checking conditions (and maybe more). One checks for null
, while the other one checks for null
and undefined
:
There are some JavaScript helper libraries like lodash that we can use to shorten the conditions:
What Is a Special Case Object?
“[…] this is the Special Case pattern where I create a special-case element that captures all the common behaviour. This allows me to replace most of the special-case checks with simple calls. […] If I need more behaviour than simple values, I can create a special object with methods for all the common behaviour. […]” — Martin Fowler, Refactoring
In simple words, regarding the null
checking condition, we create a special object for the cases when a value or an object can be null
so that a default class with common behaviour will be returned.
If we like object composition and our code is architected by many classes and interfaces, we will love this pattern.
The Problem With Null Checking Conditions
Firstly, we need to comb our code or rather related object families for null
checking conditions. After that, we need to scan these conditions for the same reactions. If it shows the same behaviour, we are lucky and we can use the Introduce Special Case pattern for this.
In the following example, we can see how I handle null
in the same way but with different context data in the else
-sector. But the content is the same — there is no data:
There could be more places with no-weather-checks
in the code base.
In my example, I need to implement only three attributes: title
, state
, and temperature
. So if we have many places where a check against null
is required and we need to assign many attributes with special values, this maintenance effort to change the code could be very high.
In summary, this approach is not DRY and the maintenance effort is draining when we need to modify or extend the assignment of the attributes.
The Power of Object Composition
In many cases, we have classes, interfaces, or other modules to represent a business case. In the example above, we have a weather
class or a weather
interface and fill the weather
object with user-readable data to explain that there is or is no data.
So we have got nice class architecture conditions to use a Special Case object to refactor and to remove repeated null
checking conditions by expanding the model with a No-Weather-Data
class (or Null-Weather-Data
class). I explicitly wrote “expand” because we’re complying with the Open-Closed-Principle. If there is not a nice class architecture, we need to refactor our code to be more polymorphic. Refactoring by Martin Fowler is an instructive book that shows we how to accomplish this.
In this case, we do not need to modify the data of the WeatherData
object at runtime and we can make the attributes immutable.
In the NullWeatherData
class, we can assign fixed values for the attributes (e.g. Unknown
or Unknown weather
as the title). Whenever we need to change these values in the future, we will change them in one place only: inside the NullWeatherData
class. We will no longer need to comb the entire code base:
Now we carry on and look at the code base to see if we can replace the previous return data logic inside the service (or in other places) with the polymorphic behaviour:
Wherever we request a weather object, we need to make sure to check the data before returning an object.
We comply with the Open-Closed-Principle with a simple null
object, which is a special case of the Special Case pattern. We have got only one place where the NullWeatherData
is created and maintained. Its only responsibility is to return the context that there is no weather data and show this to the user. That is all.
Conclusion
With the Special Case pattern, especially the null
object pattern in this example, we can refactor null
checking conditions to comply with the Open-Closed-Principle.
Thanks for reading! Follow me on Medium, or Twitter, or subscribe here on Medium to read more about DevOps, Agile & Development Principles, Angular, and other useful stuff. Happy Coding! :)