Glance'n'grok coding

by Dan Cutting

Let’s be honest: few of us take the time to really read the code we’re working on, whether it’s our own, or — shudder — somebody else’s. We take a glance and (think) we grok it.

With that in mind, here are some programming language features that may have seemed like good ideas at the time, but now wreak havoc in our glance’n’grok culture.

Exhibit A: GOTO

What’s wrong with this code?

10 A = 0
20 PRINT A
30 IF A > 9 THEN GOTO 60
40 A = A + 1
50 GOTO 20
60 PRINT "GOODBYE"

Well, nothing, if we don’t mind wading through GOTOs and conditions and assignments to interpret the intended behaviour. (It’s also got line numbers, AND IT’S A BIT SHOUTY…)

We can improve on this with a for loop.

for (int a = 0; a <= 10; a++) {
  printf("%d\n", a);
}
printf("GOODBYE");

But there’s still a lot of wires hanging out… Why stop there?

10.times { |a| puts a }
puts "GOODBYE"

Dijkstra’s seminal letter was an inflection point for structured programming. It turns out we can use higher levels of abstraction for virtually everything GOTO was once used for.

Enhance your glance.

Exhibit B: equal floats

Ugh. So many junior programmers get caught out by this. I did. Sometimes I still do.

Due to an implementation detail of how computers usually represent real numbers, this (contrived!) code may not do what it appears to do.

let x = 999.999999999
let y = x * 10000 / 10000
print(x == y ? "Equal!" : "Unequal")

But how can we fix this? We could take a leaf out of ML’s book and just make it a build-time error to test floats for equality.

Another bad idea that’s similar to float equality is permitting integer overflow. We’ve got a really big number, we add another really big number to it, and…

Some languages, like Lisp, will dynamically change the representation under the covers to make sure we don’t overflow. Others will just blow up with an exception or, worse yet, leak abstractions all over us and silently return completely wrong values. Yuck.

Exhibit C: null

Tony Hoare’s billion-dollar mistake was the null reference.

Null’s great though, right? We use it all the time to mean “no value”. That can be useful when we write a function that may or may not return a result.

But there’s no way to look at a signature and know if it will give us a real result, or a null landmine. So we add boilerplate defensive code (when we remember). Or else crash (sometimes).

public String description() {
  // ...do some complicated things that may or may not actually return a string.
}

// Might crash, so add boilerplate.
String desc = description();
int length = 0;
if (desc != null) {
  length = desc.length();
}
System.out.println(length);

Turns out this idea of “no value” can be better modelled by a sufficiently powerful type system (e.g., optionals in Swift). A clever compiler can prevent cases where a null value might be returned. No more boilerplate or landmines.

func description() -> String {
  // Must return an actual string.
}

// Will never crash.
let length = description().characters.count
print(length)

Exhibit D: dynamic scope

Quick, what’s this code do?

let y = 2
let n = addOne(5)
print(n)

func addOne(x: Int) -> Int {
  var y = 1
  return addSomething(x)
}

func addSomething(x: Int) -> Int {
  return x + y
}

If you said, “it will print 7, duh, and btw that’s a bad name for a function that doesn’t really add 1”, then you’re lucky never to have been sullied by the mind-bending concept of dynamic scoping.

7 would be the right answer in most languages, but dynamically scoped languages would print 6.

This seems pretty broken to many programmers. Yet what we consider the usual “sane” (or lexical) form of scoping is not the only way to do things.

Dynamic scoping searches the call stack for variable bindings, rather than the scope as it appears in the code itself. This means functions can behave differently depending on the state of the caller. In this case, the value of y on line 6 supersedes the value of y on line 1 when the call to addSomething() takes place.

This is a bit like global variables, but weirder, because things do still go out of scope, reverting to the shadowed values of the variables further down the call stack as functions return.

Oh and while we’re at it, globals are also a bad idea. (We know this because PHP introduced superglobals to ensure it retained its top spot on the awful languages leaderboard.)

Exhibit D: copy/paste

What could be worse than runtime null crashes or spaghetti GOTOs?

How could anything top the weirdness of dynamic scoping or leaky arithmetic?

How about the ability to generate giant swathes of repetitive code with virtually no thought or effort, and no immediate downsides, that hurt the generations of programmers who follow in your selfish, immature footsteps in increasingly maddening and subtle ways?

That’s right, I’m looking at you, copy/paste.

Larry Tesler and Tim Mott are often considered the father of cut/copy/paste, which formed part of their research at Xerox Parc. When Steve Jobs stole their work (in the “great artists” sense), the Lisa and Mac computers popularised the operation.

I wouldn’t say copy/paste is inherently evil. Like the toothbrush moustache, it merely sits upon the lip of evil.

But it is insidious. It lulls us into a false sense of glance’n’grok. “This code looks like that code, so it probably does the same thing”. But the longer it’s been there, the less true that is.

Would we really replicate a code block seven times if we had to type it all out by hand? Maybe we’d just write and call a single function instead…

I’m taking a stand. I just unmapped the keyboard shortcut for “Copy” from Xcode’s menu (Preferences… > Key Bindings).

Etc.

You can find a stack of other exhibits in Niklaus Wirth’s Good Ideas, Through the Looking Glass, which is equal parts fascinating, amusing and depressing.

Got any other favourite transgressions? @dcutting #glancengrok

Hi, I'm Dan Cutting. I mainly write Swift for iOS and Mac but love playing with Metal and programming languages too.

Follow me on Twitter or GitHub or subscribe to the feed.