-
-
Save getify/c548116927eb896f1276 to your computer and use it in GitHub Desktop.
how `let` can be taxing on refactoring inside a function
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
what this `foo` function does is arbitrary, made up, and irrelevant. it's intended to | |
illustrate some if-forked parsing logic for some string value, to do diff tasks based | |
what's in the value. | |
here is the version of the `foo` function using common function-global `var` style for | |
some temporary values, so they're available across various if-blocks in the function. | |
*/ | |
function foo(bar) { | |
var tmp, tmp2; | |
if (bar) { | |
tmp = bar.split(";"); | |
if (/^\d+$/) { | |
tmp2 = tmp[0].split("_"); | |
if (tmp2.length > 2) { | |
bar = tmp2[tmp2.length-1]; | |
} | |
else { | |
bar = null; | |
} | |
} | |
else { | |
bar = tmp[0]; | |
} | |
} | |
if (bar) { | |
console.log("bar: " + bar); | |
} | |
else { | |
console.log("bar was invalid"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
now, 6 months later, someone else comes along and needs to refactor `foo`, to add some | |
additional parsing rules on the `bar` value. they're going to need some more if-statements. | |
no problem! | |
common function-global `var` usage doesn't add any extra effort or thought in the `foo` | |
refactoring. | |
remember, don't consider what it's like to write this `foo` from scratch, but what steps | |
you'd do to REFACTOR it from "1a.js". No worrying about the scope of `tmp2` is necessary | |
here. | |
compare "1a.js" to this "1b.js". | |
*/ | |
function foo(bar) { | |
var tmp, tmp2; | |
if (bar) { | |
tmp = bar.split(";"); | |
if (/^\d+$/) { | |
tmp2 = tmp[0].split("_"); | |
} | |
else { | |
tmp2 = tmp[1].split("~"); | |
} | |
if (tmp2.length > 2) { | |
bar = tmp2[tmp2.length-1]; | |
} | |
else { | |
bar = null; | |
} | |
} | |
if (bar) { | |
console.log("bar: " + bar); | |
} | |
else { | |
console.log("bar was invalid"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
here is the version of the original (pre-refactored) `foo` function, but using `let` style | |
declaration for some temporary values. | |
in isolation, this style is a bit cleaner, since it keeps temporary variables `tmp` and | |
`tmp2` scoped to the smallest/nearest containing block necessary, instead of "polluting" | |
the function's main scope. | |
*/ | |
function foo(bar) { | |
if (bar) { | |
let tmp = bar.split(";"); | |
if (/^\d+$/) { | |
let tmp2 = tmp[0].split("_"); | |
if (tmp2.length > 2) { | |
bar = tmp2[tmp2.length-1]; | |
} | |
else { | |
bar = null; | |
} | |
} | |
else { | |
bar = tmp[0]; | |
} | |
} | |
if (bar) { | |
console.log("bar: " + bar); | |
} | |
else { | |
console.log("bar was invalid"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
now, 6 months later, someone else comes along and needs to refactor `foo`, to add some | |
additional parsing rules on the `bar` value. they're going to need some more if-statements. | |
here's the `let` version refactored. | |
remember, don't consider what it's like to write this `foo` from scratch, but what steps | |
you'd do to REFACTOR it from "2a.js". | |
compare "2a.js" to this "2b.js". | |
in this case, in addition to the refactor logic you applied in the "1a.js" --> "1b.js" | |
conversion, you also have to realize that you need to separate the initial compound `tmp2` | |
declaration+initialization from "2a.js". instead, you have to insert an extra `let tmp2;` | |
empty declaration **in a higher level of the if-statement structure**, so `tmp2`s scope can | |
extend across several blocks. | |
THAT is the "mental tax" I'm talking about. It's obviously not terrible here. But it IS | |
extra mental work in the refactor. | |
*/ | |
function foo(bar) { | |
if (bar) { | |
let tmp = bar.split(";"); | |
let tmp2; // realizing you need to add this line *HERE* is the extra "mental tax" | |
if (/^\d+$/) { | |
tmp2 = tmp[0].split("_"); // we had to separate declaration and initialization here | |
} | |
else { | |
tmp2 = tmp[1].split("~"); | |
} | |
if (tmp2.length > 2) { | |
bar = tmp2[tmp2.length-1]; | |
} | |
else { | |
bar = null; | |
} | |
} | |
if (bar) { | |
console.log("bar: " + bar); | |
} | |
else { | |
console.log("bar was invalid"); | |
} | |
} |
@artiegold this particular example might have been able to use the ?: ternary operator. but in general, if/nested-if/else-if/else structures are quite useful when you're doing more than just a single assignment as a result of the condition. In the case of assignment, even with a bunch of nested logic, yes that could decompose into a several ?: ternary operators strung together, but I also think a lot of devs (myself included) don't enjoy the extra mental effort to keep operator precedence in those cases fully straight: foo = a ? b : c ? d ? e : f : g
While that's certainly more terse, it's definitely a bit harder to glance at the structure and know the relationship of conditions to the final assignment value.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I have frequently wondered if the
pattern isn't just fundamentally broken. Either 'if' returning a value or the ternary '? :' operator is typically available. Why is there a long-standing opposition to using such constructs?