Skip to content

Instantly share code, notes, and snippets.

@getify

getify/1a.js Secret

Created November 13, 2012 13:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save getify/c548116927eb896f1276 to your computer and use it in GitHub Desktop.
Save getify/c548116927eb896f1276 to your computer and use it in GitHub Desktop.
how `let` can be taxing on refactoring inside a function
/*
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");
}
}
/*
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");
}
}
/*
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");
}
}
/*
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
Copy link

I have frequently wondered if the

if (something) {
   a_var = a_value;
} else {
   a_var = another_value;
}

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?

@getify
Copy link
Author

getify commented Nov 14, 2012

@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