The Last Line Effect
software.intel.com
software.intel.com
I was thinking about this article recently after I realized I had made this mistake and was wondering if there'd be any consistent way to check for this, like some kind of clippy: "so it looks like you're copy-pasting a pattern incorrectly..."
When I am looking at someone else's code that was built mostly by copy-and-paste, it is jarring to have changes in style, indentation, and quality from piece to piece. Also there tend to be lines of code that don't do anything. Especially beginners will just paste code and then change it until it "works", which leaves a incredible mess.
Several of his examples involve duplicate lines or blocks, but he considers the error to occur in the last duplicate, rather than the first.
It seems equally valid to consider that an error on the previous lines, and more valid to consider it an error equally shared among all duplicated blocks.
Also, a simple hypothesis for why errors show up at the end: We tend to add new cases at the end of a block of similar code, and bugs are more likely to exist in more-recently added code because it's had less time to be discovered and fixed.
No, because people usually write it top-down.
So, if I have to do something like:
a1 = 5 * b1 + 1; a2 = 5 * b2 + 1;
for multiple items, I'm going to copy one of the lines written manually then paste it at the end (because the first ones are right)
Also paste more lines than needed (because it's an estimate) is a common bug, and you'll end up with a redundant line at the end.
A similar effect is witnessed with crime and when I worked in retail we had training to help us focus on the first and last 30 minutes of the day as these were when staff (for whatever reason) paid least attention and it was believed that this was known and taken advantage of.
It's just a human pattern, that appears in many tasks we undertake. It doesn't surprise me that it appears in coding.
ax[i] = bx[i]
ay[i] = by[i]
az[i] = bz[i]
Can the languages used write this in a better way?I do agree that having inner functions and making them easy to define is better, but it's not always possible. In F# I could write:
let assign i (a,b) = a[i] <- b[i]
[ax,bx; ay,by; az,bz] |> Seq.iter (assign i)
But that's allocating a list (and if I closed over i, it'd allocate a closure too). Really good macro support would fix this, though. And it's only marginally better in this case. (For more elaborate cases it might be easier to scan.)Now I simply abuse the compiler's ability to inline small functions automatically and I create functions for every single repetition pattern I find.