When things go wrong
How I think about it
There are two kinds of “wrong”, and I treat them completely differently.
A mistake is fatal — and avoidable. Calling something that doesn’t exist, reaching past the end of a list, dividing by zero: these mean the program itself is wrong. I stop, right there, and say why. I don’t offer a way to carry on regardless, because there’s nothing sensible to carry on to. But every one of them can be avoided by the program: check the length first, check for zero first, spell the name right.
Everything else is a result. The player typed a name nobody has. The save file isn’t there. The shop is out of potions. That’s the world being the world, and a good program expects it. So a function returns those, the same way it returns anything else, and whoever called it looks at what came back.
That’s why I have no try, no catch, no ON ERROR. There isn’t a special
second way for things to come out of a function. There’s one way, and the
“it didn’t work out” answers travel along it like every other answer.
The rest of this lesson is those two ideas, one at a time.
Mistakes I catch before running
Most mistakes never get as far as running. I read everything first.
FUNCTION area(width, height)
RETURN width * height
END FUNCTION
PRINT "this line never runs"
PRINT aera(3, 4)
Nothing was printed, not even the first line, because aera isn’t anything.
Fix the spelling over there and try again.
Mistakes I can only catch while running
Some can’t be seen until the numbers are real. These stop the program where they happen.
scores = [10, 20, 30]
PRINT "about to go wrong"
PRINT scores(3)
PRINT "this never prints"
Fatal, yes — but look how easy it was to avoid. The program knew how long the list was all along. It just didn’t ask.
scores = [10, 20, 30]
wanted = 3
IF wanted < LEN(scores)
PRINT scores(wanted)
ELSE
PRINT "there's no score number", wanted
END IF
parts = 0
IF parts = 0 THEN PRINT "nothing to share out" ELSE PRINT 100 / parts
there's no score number 3
nothing to share out
That’s the deal with mistakes: I won’t let one slide, and you never have to make one.
Results that say “it didn’t work out”
Now the other kind. A function can hand back an error instead of an answer. First it has to
declare the error, in its doc comment: a name, what I should say, and
optionally a tip. Words in {braces} are filled in when it happens.
/// Finds a player's score.
/// !player-unknown: Nobody called `{name}` is playing!
/// !tip: The players are ada and grace.
FUNCTION score_of(player)
LOCAL scores = {ada: 12, grace: 30}
IF scores(player) = NULL THEN RETURN ERROR("player-unknown", {name: player})
RETURN scores(player)
END FUNCTION
PRINT score_of("ada")
found = score_of("linus")
IF ISERROR(found)
PRINT "no such player, carrying on"
ELSE
PRINT found
END IF
12
no such player, carrying on
An error is a value. You can hold it, test it with ISERROR, and decide
what to do. All is well: a question was asked, and “nobody by that name” was
the honest answer.
A result nobody looked at becomes a mistake
Here’s where the two ideas meet. If an error is used as if it were an ordinary value — printed, added to, passed along — then the program stops, with the message its function declared.
/// Finds a player's score.
/// !player-unknown: Nobody called `{name}` is playing!
/// !tip: The players are ada and grace.
FUNCTION score_of(player)
LOCAL scores = {ada: 12, grace: 30}
IF scores(player) = NULL THEN RETURN ERROR("player-unknown", {name: player})
RETURN scores(player)
END FUNCTION
PRINT score_of("linus") + 10
Not looking at a result that could be “it didn’t work out” is a mistake in
the program, so it’s treated like one: fatal, and avoidable. The fix is the
ISERROR check from the example before.
So you check the results you can do something about, and anything you
didn’t check stops the program by itself, with a proper explanation, at the
first place it mattered. Nothing is ever quietly swallowed, and there’s no
try to wrap around things.
From Python and JavaScript: No exceptions flying up the stack. An error comes back from the call that made it, and it’s yours to look at. Ignore it and it stops the program at the first place it’s used.
From Lua: Like returning
nil, err, except you can’t forget to check: the error is the value.
From BASIC: No
ON ERROR GOTO. The check goes right where the thing might fail.
Crash-tan’s Tip: Write the message for the person who’ll be reading it at the worst possible moment. Say what you were looking at, then what to try. That’s what I do!