JavaScript/ECMAScript/TypeScript are officially everywhere these days and with them comes the idiomatic use of truthiness checking.
At work, recently I had to fix a nasty bug where the truthiness of an optional value was used to determine what "mode" to be in, instead of a perfectly-good enumerated type located nearby. Let me extrapolate this into a worked example that might show how dangerous this is:
type VehicleParameters = { roadSpeed: number; engineRPM: number; ... cruiseControlOn: boolean; cruiseControlSpeed: number | undefined; }and imagine, running a few times a second, we had a function:
function maintainCruiseSpeed(vp: VehicleParameters) { const { roadSpeed, cruiseControlSpeed } = vp; if (cruiseControlSpeed ?? cruiseControlSpeed < roadSpeed) { accelerate(); } }
Let's suppose the driver of this vehicle hits "SET" on their cruise control stalk to lock in their current speed of 100km/h as their desired automatically-maintained speed. The control module sets the cruiseControlOn boolean to true, and copies the current value of roadSpeed (being 100) into cruiseControlSpeed
Now imagine the driver disengages cruise control, and the boolean is correctly set to false, but the cruiseControlSpeed is retained, as it is very common for a cruise system to have a RESUME feature that goes back to the previously-stored speed.
And all of a sudden we have an Unintended Acceleration situation. Yikes.
As simple as can be, but no simpler
Don't get me wrong, I like terse code; one of the reasons I liked Scala so much was the succinctness after escaping from the famously long-winded Kingdom of Nouns. I also loathe redundant and/or underperforming fields, in particular Booleans that shadow another bit of state, e.g.:
const [isLoggedIn] = useState(false); const [loggedInUser] = useState(undefined);
That kind of stuff drives me insane. What I definitely really like is when we can be Javascript-idiomatic AND use the power of TypeScript to prevent combinations of things that should not be. How?
Typescript Unions have entered the chat
Let's define some types that model the behaviour we want:
- When cruise is turned on we need target speed, there's no resume speed
- When cruise is turned off we zero the target speed, and the resume speed
- When cruise is set to coast (or the brake pedal is pressed) we zero the target speed, but store a resume speed
- When cruise is turned on we need a target speed to get back to, and there's no resume speed
type VehicleParameters = { roadSpeed: number; engineRPM: number; cruiseControlSettings: CruiseControlSettings; } type CruiseControlSettings = CruiseOnSettings | CruiseOffSettings | CruiseCoastSettings | CruiseResumeSettings type CruiseOnSettings = { mode: CruiseMode.CruiseOn targetSpeedKmh: number; resumeSpeedKmh: 0; } type CruiseOffSettings = { mode: CruiseMode.CruiseOff targetSpeedKmh: 0; resumeSpeedKmh: 0; } type CruiseCoastSettings = { mode: CruiseMode.CruiseCoast targetSpeedKmh: 0; resumeSpeedKmh: number; } type CruiseResumeSettings = { mode: CruiseMode.CruiseResume targetSpeedKmh: number; resumeSpeedKmh: 0; }
Let's also write a new version of maintainCruiseSpeed, still in idiomatic ECMAScript (i.e. using truthiness):
function maintainCruiseSpeed(vp: VehicleParameters) { const { roadSpeed, cruiseControlSettings } = vp; if (cruiseControlSettings.targetSpeedKmh < roadSpeed) { accelerate(); } }
And finally, let's try and update the cruise settings to an illegal combination:
function illegallyUpdateCruiseSettings():CruiseControlSettings { return { mode: CruiseMode.CruiseOff, targetSpeedKmh: 120, resumeSpeedKmh: 99, } }... but notice now, you can't; you get a TypeScript error:
Type '{ mode: CruiseMode.CruiseOff; targetSpeedKmh: 120; resumeSpeedKmh: number; }' is not assignable to type 'CruiseControlSettings'. Types of property 'targetSpeedKmh' are incompatible. Type '120' is not assignable to type '0'
I'm not suggesting that TypeScript types will unequivocally save your critical code from endangering human life, but a little thought expended on sensibly modelling conditions just might help.
No comments:
Post a Comment
Comments welcome - spam is not. Spam will be detected, deleted and the source IP blocked.