CodeSOD: Confessions of a Deep Copy

While JavaScript (and TypeScript) may have conquered the world, those languages have… unusual conventions relative to some lower level languages you might encounter. I recently was having a debate with a C-guru, who lamented all the abstractions and promised that well written C-code would forever be faster, more compact, and easier to read that the equivalent code in a higher level language.

That may or may not be true (it's not), but I understand his point of view.

Lily comes from a background where she's used to writing lower level code, mostly C or C++ style languages. So when she tried to adapt to typescript, the idea that everything was a reference was inconvenient. An example of a thing which flummoxed her:

let inner = [1,2,3,4,5]; let outer = []; outer.push(inner); inner.length = 0; //oops, outer[0] is now an empty array

Since outer contains a reference to inner, changing inner means that outer also gets changed. That wasn't the behavior that Lily wanted, and she was new to JavaScript. So she did what any of us might do in that situation: she searched MDN for some sort of Array.prototype.deepClone() method. She didn't find one, and was out of things to Google, so she just did her best and invented her own "deep clone" method for parsing some code.

private splitTokenByPhysicalEOL(): Array<Token[]> { const ret: Array<Token[]> = []; const cur: Token[] = []; for (const token of this.tokenList) { cur.push(token); if (token.type === 'EOL') { ret.push(JSON.parse(JSON.stringify(cur))); // ??? cur.length = 0; } } if (cur.length) ret.push(cur); return ret; }

This code is part of a parser. Lily wanted to return an array of lines, where each line was an array of tokens for the parser to use. At the end of a line, she wanted to take the tokens parsed so far, put them into the result set, and then clear out cur to restart the next line.

Now, since cur was declared const, she couldn't do cur = [], which would create a new object and a new reference, so she did the cur.length = 0 trick to empty the array. But doing that modifies the array in place, which means all references to the array, including the ones in ret, also get cleared.

Which brings us to this line: ret.push(JSON.parse(JSON.stringify(cur))); // ???, which was Lily's attempt at a deep copy. Not a terrible attempt, honestly, given the things she didn't know and JavaScript's… idiosyncrasies. Turn the array into a JSON string, parse the JSON string. Voila, a copy! The comment is a helpful note from her mentor on the project, who suggested that there were better ways to deep copy in JavaScript, or avoid needing to copy at all by just spawning new references as needed.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

This post originally appeared on The Daily WTF.

Leave a Reply

Your email address will not be published. Required fields are marked *