Every now and then I come across a section of code that looks weird to me, and I have to spend an unnecessary amount of time to sort it out. This angers me a bit inside, every time. I don't like wasting my time on things my computer could do for me.

An Example

In the case that triggered this article the weird code I found was something along the lines of

if profile and profile.verified_email: last_interaction = profile.email.interactions()[0] # some more code accessing various fields and methods of profile if profile.verified_email and last_interaction: # what!?

In case it's not clear already, I'll explain my confusion.

One of the basic tools when writing "proofs" (either in a very rigorous sense or in the more loose, intuitive sense) for computer programs is that inside an if construct, whatever was the condition that led there can be assumed to be true – because there is no other way to get into that block of code!

So we can assume that inside the outer if block, the profile.verified_email field is true-ish (given that this is Python where a lot of things count as true in a conditional).

Despite this, the profile.verified_email field gets re-tested inside the if block where it is supposed to be true. There are three possible explanations for this, in order of likeliness:

The programmer who wrote this code was tired/lazy/distracted/drunk. Very likely. They just didn't realise they were re-checking a variable that is already true. The profile.verified_email is not actually a variable field, but rather a property, which means it can do all kinds of side-effects when you access it. Perhaps it queries a server and confirms verification each time it's accessed, and thus can change at any point? The code in between the two checks, the code that uses the profile object, might have a side effect that makes the email unverified even when it was verified previously.

So while explanation one is the most likely one, the other two can't be dismissed. And unfortunately, they lead to different actions on my part. If explanation one is correct, then I should remove the extra check from the code, because it's confusing and useless. If explanation two or three are correct I should leave it in.

Sooo… what do I have to do? Read lots of code. To begin with, I only have to read the implementation of profile.verified_email to see whether or not it's a property. If it's not, I can quickly rule out explanation two. Then I have to read the code and the implementation of the methods used in between the two checks, to rule out any side effects causing a verified email to become unverified.

You might not think of it that way, but reading all that code is a huge time waste. There is a much better way of doing it. There is a way of doing it that means I can instantly know whether or not it's possible for profile.verified_email to change between the two checks. If it's not possible (which was the more likely scenario) then I can immediately remove it from the second check. No code reading required.

What is this magic I speak of? Controlled side effects.

Side Effects

Quick recap: side effects are anything your code does that is not straight-up computing a value. So things like modifying object fields or global variables, reading a file, querying a server, generating and using random numbers… you get it.

Any time you get different results by reordering method calls or call a method twice, you're witnessing side effects. Any time you groan about having to set up state to unit test a method you're witnessing side effects. Any time you change one part of your program and another starts misbehaving you're witnessing side effects.

Any time you hear someone speak of isolation, separation of concerns, implementation hiding and low coupling, you're hearing them talk about minimising side effects. Side effects are the things that intertwine unrelated parts of your program.

Since side effects generally make it harder to write bug-free code we tend to avoid them when we can. The problem is that Python (and indeed most languages) give us no tools to figure out when side effects are happening.

Controlled Side Effects

Modern type systems allow you to annotate your values and methods with all kinds of things, which is great. Specifically, some type systems allow you to annotate your values and methods as "doing side effects". There are different kind of side effects which have different annotations, but let's just speak broadly about the concept.

Not only do these type systems allow you to annotate things as "doing side effects"; they specifically disallow doing side effects unless you have annotated the thing as such.

So what we have then is a bunch of methods that are annotated as doing side effects – these are "dangerous" territory. You have to be careful around these because they might do things you don't expect. But we also have even more methods that are not annotated as doing side effects. These methods are called pure. If the methods you're using are all pure, you know you can use regular proof techniques when trying to figure out stuff about your program.

In the example that opened this article, had all methods used between the checks of profile.verified_email been marked as not doing side effects (as well as the profile.verified_email field), I would have instantly known that profile.verified_email has to be true throughout the outer if block, and the previous programmer had just been tired/lazy/distracted/drunk.

No code reading required.

I like it when my computer does that kind of work for me so I don't have to. It's a shame very few languages give us those tools.

But …

Sometimes when presented with "new" ideas (scare quotes because annotating side effects is a fairly old idea in the grand scheme of things – it's just not widely practised) on how to do things, people come up with all kinds of reasons to not do things the "new" way, because it's not the good old way they know. These are a few arguments I've heard against annotating things that do side effects: