Making laws is similar to writing code. In Part 1 I’ll talk about how law and code both rely on the power of abstract language.
(Note: You don’t need coding experience to read this.)
It’s Hard to Know the Future
Laws and code are instructions for solving problems. Laws tell people how to solve problems, while code tells computers how to solve problems. For example, a law will tell two drivers at an intersection who can go first. Code will tell a computer how to add two numbers.
There’s a central challenge when creating these instructions. When we write laws or code, we can’t know all the details about the problem we’re trying to solve. Yet we still have to provide a solution.
That may sound strange, but think about this example. Suppose we need to make a law that says what speech is allowed. If you tried actually writing this law, you’d quickly realize it’s impossible for you to foresee every single problem that could arise from people’s use of speech. That makes writing the law much more difficult.
What do we do then? First, I’ll talk about how we deal with this in law, and then in programming.
The Problem in Law
Laws are meant to resolve conflicts between people.
When we make these laws, we can’t specify how to resolve every potential conflict. There are too many possibilities. What we do instead is write general, abstract laws. Then, when actual conflicts occur, judges decide how to apply those abstract laws to the specific circumstances.
Here’s an example. Let’s say we didn’t want people driving their vehicles in a public park. We could write the law by specifying every type of vehicle that’s banned.
Banned Vehicle List:
1. Cars
2. Trucks
3. ATVs
4. Motorcycles...and so on...
There’s a problem. No matter how hard we try, inevitably someone will use a “vehicle” that doesn’t fit into our list. What about Segways? Construction trucks? We are bound to leave something off.
The Abstract Approach
It would be better to write a general, abstract law that just says, “No vehicles in the park.”
If there’s a conflict, and it’s ambiguous whether the vehicle should be banned, we can resolve it by having a judge decide whether the law’s abstract language includes the vehicle in question.
This approach lets us avoid trying to list out every vehicle we can think of.
The Downsides
There are trade offs with this approach.
One downside is abstract laws aren’t as clear. For example, the abstract version of the law still isn’t clear on whether Segways are banned. This can leave people guessing on what’s actually allowed.
Another downside is we have to interpret the law through court trials. That requires time and resources.
An Example
Let’s go through a full example of how this works in practice. Suppose a park employee driving their golf cart ran over someone in the park. It was a complete accident—no foul play, no negligence. Typically, the employee would not be liable for the injury. However, there is a law that says “No vehicles in the park.” Does that include park employees driving their golf carts? If so, the park employee will be held liable.
Because this law is relatively vague, a judge has to interpret it in a court trial where both parties will argue it should go their way. This adversarial process can be costly. Had the law specified in the first place whether employee golf carts are allowed, we could have avoided it.
But could we have predicted this problem when writing the law? Hard to say.
Lesson from Law
Here’s the overall lesson: abstract language helps us cover a wide variety of situations that we could never foresee. However, applying those abstract laws to specific conflicts comes with its own costs.
The Problem in Programming
Programmers face a similar dilemma. They write algorithms to solve problems, similar to how lawmakers write laws to resolve human conflicts. When they write an algorithm, they have two choices.
(1) They can write a bunch of specific algorithms that each solve one problem at a time.
(2) Or they can write a single generic algorithm that solves all their current problems, and related problems that may arise in the future.
On the surface option (2) sounds better. But I’m sure you know it’s more complicated than that.
When programmers write generic algorithms, we say they are making their code more “abstract”. When programmers write code that’s more abstract, that code is able to solve a wider range of problems.
An Example
Suppose a programmer needed to calculate the California sales tax. One solution would be to write an algorithm that required the total sale amount as an input. Then the code will multiply that sale amount by the California sales tax (8.5%). Problem solved.
calculateCaliforniaSalesTax(input1: saleAmount) {
return saleAmount * 0.085
}But chances are the programmer will need the sales tax for other states as well. He could write out separate algorithms specific to each state, similar to how lawmakers may try to list out every vehicle banned in the park.
calculateCaliforniaSalesTax(input1: saleAmount)
calculateUtahSalesTax(input1: saleAmount)
calculateNewYorkSalesTax(input1: saleAmount)
calculateFloridaSalesTax(input1: saleAmount)
...But writing all that code (and keeping it updated) will be a lot of work. And besides, how do we know it will be restricted to the U.S.? At some point he’ll probably need the sales tax for a country, province, or city he has never heard of.
The Abstract Approach
Rather than writing a separate algorithm for every tax rate, he can write a single algorithm that’s more abstract. He could do that by adding a taxRate input to the algorithm.
calculateSalesTax(input1: saleAmount, input2: taxRate) {
return saleAmount * taxRate
}In our original algorithm, we only inputted the sale amount. But in the abstract algorithm we input both the sale amount and the tax rate (e.g., the taxRate for California would be 0.085).
The upside is we now have an algorithm that can calculate the sales tax for any place in the world. No matter what tax rate we need to use, our algorithm can handle it. We just need to input that place’s tax rate.
The Downside
So what’s the catch this time?
The catch is any time a programmer wants to use our algorithm to calculate the sales tax, they have to come up with the tax rate. It’s sort of a pain to do that every single time.
Similar to how the abstract law put a greater burden on the judge to figure out what a “vehicle” is, our abstract sales tax algorithm puts a greater burden on the programmer to figure out what the tax rate is.
Maybe that one example doesn’t seem like a big deal. But imagine if every algorithm in our application is generic like this one. The code will be a huge pain to work with.
The Programming Lesson
The lesson is very similar to law. Making code more abstract lets us use our code to solve a wider range of problems. But using that abstract code to solve any specific problem becomes less convenient.
Striking a Balance
Which approach is better? Should we write laws and code for every specific situation, or should we make our language as abstract as possible? The answer, of course, is in the middle.
Let’s see how we could we improve the law and code from our previous examples.
Modifying the Vehicles Law
For the vehicles law, we could modify it by adding a few specific qualifiers:
“No motorized vehicles in the park, except for those used by park employees.”
Here we’ve kept our abstract “vehicles” term, which lets us avoid the exhaustive list. But we’ve also added a “motorized” qualifier which helps restrict it to the category of vehicles we’re probably most concerned about. And lastly, by adding an exception for park employees, we let them do their job and avoid unfair liability.
Is it perfect? No. We still don’t know if our Segways are okay. But these extra qualifiers can help us avoid a lot of costly court trials that would likely happen in their absence.
Modifying the Sales Tax Algorithm
We can improve the sales tax algorithm by making that taxRate input a little less abstract. Instead of letting other programmers insert any number for the tax rate, we could require them to input a region instead (e.g., california). Then the algorithm will take care of finding that region’s tax rate.
caclulateSalesTax(input1: saleAmount, input2: region)Much nicer.
The region input makes our algorithm more specific to our problem, as opposed to an algorithm that just multiplies any two numbers. But it’s not so specific that it will be useless when we need to calculate the sales tax for a new region.
Conclusion
Abstract language helps us cover a wide variety of situations that we could never foresee. However, applying that language to specific situations has costs. We want to strike a balance between abstraction and specificity.
To do that, we need to find the boundary space of our problem, and then find the language that’s just abstract enough to fill that space.
Roadmap
Part 2–Bottom-Up Programming and the Common Law
Part 3–Refactoring and Reforming
Part 4–Microservices, Transaction Costs, and Federalism