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:
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 (don’t cache, minimize mutable state)
- 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, pick one and derive the other:
struct Temp {celsius: float}
status(t: Temp) { print(t.celsius, c_to_f(t.celsius)) }
- This also reduces invariants. Now you don’t have to ensure celsius is always == fahrenheit
- Floating point accuracy can also cause desynchronization
- What if you convert 20C ⇒ 68F ⇒ 19.99999C ≠ 20C
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)
- This controls access to the parent struct better and makes it obvious when a child is mutating it’s parent
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 condition
if x: return; // => x is false for rest
Don’t optimize your program until it’s complete and correct.
- It’s much easier to optimize a simple+correct program than it is to fix a complex+incorrect one
- For example, use linear search instead of binary search
- Unoptimized programs have simple structs and algorithms
- Optimizations create invariants: a BST is harder to maintain than an array, even more so if the BST is a view into the array and both need to be synchronized
If you have any more please email me, I’m definitely missing some.