YesThatBlog.Com

Lovingly hand-crafted in small batches just for you by me.

Jun 12, 2020 - 3 minute read - Comments - technology

Go has eliminated most of my off-by-one errors except 0... I mean 1.

Go’s range operator (for x = range foo) has eliminated the most common trap where I make off-by-one errors. The next largest category of off-by-one errors would be eliminated if there was a way to specify the last item in an array. It would also improve a developer’s ability to convey intent.

Speaking for myself, the biggest category of potential off-by-one errors is when constructing “for” loops.  Go’s range operator eliminates that entire category of potential mistakes.  For that I’m thankful.

So what is my next largest category?

Again, speaking only for myself, it is the fact that the last item in an array is len(a)-1. It’s easy to remember to subtract 1, but when it’s buried in the middle of a formula, it’s non-obvious and obscured.  More importantly, it hides intent.

For example often the -1 is cancelled out by a +1 elsewhere in the formula.  Now that was originally len(a)-1-+1-y becomes len(a)-y.  Original intent obscured.

Or worse, a formula like len(a)-1-y becomes len(a)-z because someone noticed that z==y-1 and made the substitution. Yes, it is a premature optimization but I bet you’ve done a similar substitution many times.

Oh wait. Did you notice the typo in the previous paragraph? The substitution would be valid if z==y+1, not z==y-1.

The fact that you didn’t notice proves my point. Now do you understand my pain?

Luckily I have a simple proposal that would fix this.

What if there was a built in function highest() that was like len(), but returns the highest valid index. Sure, it is nothing more than a substitute for len(a)-1. Not a super huge difference, but I think it would eliminate this class of off-by-one errors.

My example from before becomes:

  • OLD: len(a)-1-+1-y
  • NEW: highest(a)+1-y`

The second example loses the temptation for premature optimization:

  • OLD: len(a)-1-y
  • NEW: highest(a)-y

Example: The classic Go “Pop from a stack” idiom is improved:

  • OLD: x, a = a[len(a)-1], a[:len(a)-1]
  • NEW: x, a = a[highest(a)], a[:len(a)-1]
  • Note: Intention is clearer: x is the last element, a has a reduction in the length by 1.
  • NEW2: x, a = a[highest(a)], a[:highest(a)]
  • In this case the intention is even more clear: x is the last element, a is all but the last element.

Loop through elements in reverse order:

  • OLD: for i := len(s)-1; i >= 0; i-- {
  • NEW: for i := highest(s); i >= 0; i-- {
  • Note: The intention is much clearer.

I realize that the go project is very reluctant to add new features and rightfully so.  That said, I think highest() (or maybe end()?, ultimate()?  final(), hind()?) would be a great human-centric feature to add.

Thoughts? Should I file a feature request? (Comments enabled.)