This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the workflows category.
Last Updated: 2024-11-21
I had some JavaScript UI code that essentially had three states:
I started off with the following code, taking the "post 2010" case as my basis:
if (yearFirstRegistered >= 2010) {
hide("emissions_class")
// I will refer to this as the "middle step"
} else if (yearFirstRegistered == 2009) {
show("co2_emissions")
show("month_first_registered")
show("emissions_class")
} else {
hide("month_first_registered")
hide("co2_emissions")
}
Despite being simple code, this led to inconsistent states as I cycled through settings in a random order. Why?
Ultimately this was because I took an "imperative" instead of a "descriptive" approach.
Once I was in, for example, the "2008 and earlier" state, then when I changed the state back to "2010", this goes there so directly and does not pass though the intermediate steps. i.e. the stuff in the 2009 state ("middle step") would never happen - thus the things that were supposed to get carried forwards in time from the 2009 step (e.g. "showing the month field") would not be - leading to this field being lost when arriving to the state via this route.
Even if I had not made the intermediate steps assumption, the code would also be wrong. The issue was that I took a perspective that was glued to the initial state (2010), the one that was first shown when you loaded the page. I mentally diff-ed from there to the rest. This sort of reasoning would have been OK had I reset the page to initial state each time (e.g. with a page refresh). But this wasn't possible - I was dealing with a mutated DOM. Therefore it was required for me to explicitly call commands to show what (I had initially reasoned) "should always be there"
There are three fields to be hidden/shown. Therefore a foolproof way to ensure a mistake isn't made is to insist that each is addressed in every branch :
if (yearFirstRegistered >= 2010) {
hideSectionAndRemoveValidations("emissions_class")
hideSectionAndRemoveValidations("month_first_registered")
unhideSectionAndRestoreValidations("co2_emissions")
} else if ([2008, 2009].includes(yearFirstRegistered)) {
unhideSectionAndRestoreValidations("co2_emissions")
unhideSectionAndRestoreValidations("month_first_registered")
unhideSectionAndRestoreValidations("emissions_class")
} else {
hideSectionAndRemoveValidations("month_first_registered")
hideSectionAndRemoveValidations("co2_emissions")
unhideSectionAndRestoreValidations("emissions_class")
}
The original code would have worked here (and it would have been easier to reason about)
This one is my favorite:
shown = {
2010: ["co2"],
2010: ["co2", "year", "emissions"],
2010: ["emissions"],
}
// Then some code to make this happen
Know when to take a descriptive (instead of declarative) approach to solving a problem