Reasoning About Code
Sign up for weekly Clojure tips, software design, and a Clojure coding challenge.
Summary: Functional programmers often use the term "reason about code". It's not very well defined generally, but I use it myself to refer to our ability to use our real-world intuition in our own code.
A lot of people talk about "reasoning about code". I certainly do. It's something that I don't think I ever heard when I was an OO programmer. What does it mean?
It's a tough question, because it's not one of those technical terms with a technical definition. It's just something people say when they are talking about the benefits of functional programming. But since I say it, too, I might as well give my understanding of the term.
To me, "reasoning about code" is all about the limitations of the human mind. If we were hyper geniuses, we could read any amount of code and just understand it. But we're not and we can't. As programs get bigger, we can't help but lose track of what's happening in a program.
People are used to interacting with the real world, where effects tend to be local. For instance, if I'm locked in my house, a person ten miles away cannot attack me. When we walk down the street at night, we know there are people who might hurt us somewhere in the world. But what we're concerned about is if they are close by. Instead of starting with the locations of all attackers and calculating the probability of each of them being able to harm us, we look around where we are and we evaluate the people we see. Humans think locally because that's where the action is.
It's this sense of locality that we find in functional programming languages. When you look at functional code, several things help you localize yourself:
- Scopes are localizing. Definitions cannot "leak in" from elsewhere.
- Pure functions are localizing. Pure functions will act the same regardless of when they are called or how many times they are called.
- Immutable values are localizing. No need to worry about other parts of the code modifying it.
- Isolated and consolidated side-effects. At least they're happening in the same place.
Since everything is relatively local, you have less to read and understand to be able to reason about what it's going to do. There are some common things that are non-localizing in programming languages.
- Global mutable state. Anything, in any part of the code, can change it at any time. The opposite of local.
- Scope leak. Variables can often be modified outside of their scope, or mutations in one scope are visible outside the scope.
- Mutable objects. You are referencing an object that is changing out from under you.
- Side effects. Well, once you send a message across the wire, or receive on, it's not so local.
So what should we do? Push state down from global to local. Make it as local as possible. Factor out pure functions from your code, which should isolate state change. Use immutable values whenever possible. Isolate and consolidate your side effects (so at least you know where they are happening).
People talk about "reasoning about code" a lot and it's not clear that it's meaningful. But I do use the term and when I do I mean "things are more local so I can keep them in my head". It's a notion that serves me well. For instance, it acts like a code smell. If I'm not able to keep something in my head, it's time to make it more functional.