I’ve learned a lot about programming in the past couple years and I thought I’d make a list of things to think about + avoid.
Good programs are simple and correct. Note how I didn’t include performant. Here’s a funny joke:
A reporter interviews a man who claims to be the world’s fastest mathematician.
“What is 5 times 5?” the reporter asks.
“25!” the man shouts immediately.
“What is 142 times 3,891?”
“552,522!”
“That’s completely wrong!” the reporter says.
“Maybe,” the man replies, “but I was fast!”
Here are some ways to make your programs better:
Struct design
Reduce invariants
- The less things you have to keep in order, the harder it is to write bugs
- We can do this by making invalid states unrepresentable
- i.e. Dog should never have
isLayingEggs: bool. Make that a specific field under Bird and make them both tagged enums under Animal
Disallow multiple sources of truth
- Store the minimum you need in each struct and calculate the other data (that is a function of the struct) on the fly
- Example: a thermometer app. Don’t store temperature in both celsius AND fahrenheit, instead:
status() { print(f"{celsius}, {c_to_f(celsius)}") }
- This is related to reducing invariants. Now you don’t have to ensure celsius is always == fahrenheit.
- Floating point accuracy would also kill you here.
- What if you have 20C ⇒ 84F ⇒ 19.99999C ≠ 20C
- Also from a data-oriented design perspective less variables means better cache hitrate
Avoid backwards links
- Instead of
struct Parent {child: Child}, struct Child {uplink: *Parent}
- Pass Parent as a parameter only when it’s needed
Child::whosmydad(parent: *Parent)
Things to avoid in implementation
Simplify control flow
- Avoid while loops where it isn’t obvious where you terminate
- I.e.
while x < y {x = z if a else x++}
- Strongly prefer
for x in X:, even if you need multiple
- Avoid multiple return, unbalanced paths
- Prefer
switch/match
- Early return can create a hidden invariant
if x: return // x is true for rest
Use linear search instead of binary search
- It allows much simpler structs and the performance can be similar once you factor in space usage ⇒ cache hitrate
- It also creates invariants: a BST is harder to maintain than an array, even more so if you have both and the BST is a view into the array
If you have any more please email me, I’m definitely missing some.