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

reading time 3 min

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.)

Tom Limoncelli

Recent Posts

Archive

Categories

Tags

Powered by Hugo | Theme - YesThatTheme © 2017 - 2023 Tom Limoncelli