A bit about me and my thoughts and views on software and engineering.
This is a live document; thoughts and views can change and be changed.
Make to delight, care about your work, make the world a better place, and do no evil.
-
If stuck for ideas, make something that earns people money, saves them time, simplifies a workflow, or is required for compliance.
-
Optimize the process, then work in the optimized process. Compromise when appropriate.
-
Correctly prioritize features.
- How long features take to complete often follows a Pareto distribution.
- "~80% of features take ~20% of the time."
- Focus on the 80%, with the 20% in mind for later.
-
Respect your teammate's time. Never request review of your work before you understand it and are happy with it. A PR is a proposal, not a question.
-
Optimize for the reader, not the writer. Understanding code can take 10-100x more time than writing it. The reader is another engineer, six months from now, with no context.
-
My process:
i task 0 Get requirement(s)/problem(s). 1 Make sure they are the correct ones to solve. GOTO 0 until correct. 2 Has somebody already created a solution or similar? If so, GOTO 4. 3 Figure out solution. Research. Break the problem down. What are the behaviors? Need help? 4 Iterate whilst liaising with stakeholders. Ensure on right track. Need initial buy-in? 5 Iterate through releases. alpha -> beta, 0 -> 1 -> 2. Plan path to GA. 6 Market, maintain, extend, semver, care, profit! -
Don't be religious.
- Optimal decisions almost always sit between 0.2 and 0.8 of a decision space, almost never at the extremes.
- Example: optimal amount of tech debt is never at 0% or 100%. Tech debt is like fiat debt - none or lots can both be bad.
- Be wary of anyone who is certain.
-
1000 LoC that clearly expresses intent is better than 100 equivalent LoC that expresses nothing. See ZoP PEP 20 #2.
-
Many problems are made much easier by focusing on:
- A simpler/toy problem first.
- The required behaviors.
- The required data structures and APIs/interfaces.
- The involved data ETL processes.
- The failure modes - what does broken look like?
-
Follow "LDD" - Library-Driven Development.
- "Imagine the code you are writing, whatever it is for, is a 'package'."
- This mindset promotes reusable code with room for reinterpretation.
- Useful test: could you publish this module's
index.ts(or equivalent) and feel okay about its surface area? If not, you are probably doing it wrong.
-
Name things honestly. A function called
getUserthat also writes to a cache and emits an event is a liar. Liars cause bugs. -
The cost of a feature is not just making it; it's the integral of its maintenance burden over its entire lifetime.
-
Features must pay back more in profit than they cost to make, maintain, and sunset.
-
Tests must pay back more in confidence than they cost in maintenance.
-
See a pattern?
- Prefer composition (excluding game dev).
- Inheritance often leads to complicated code. Sometimes it's the right path, but unlikely.
- Composition promotes divide-and-conquer problem solving and composable architectures.
- Follow the KISS principle (but sometimes "big" problems need "big" solutions).
- Follow the SR principle.
- Follow PEP 20 (Zen of Python).
- My highlights:
- #2 - Because implicit code is insidious.
- #5 - Because flat lists of operations are more readable and composable than deep stacks, and promote divide-and-conquer.
- #12 - Because decisions without data are guesses.
- #13 - Because Go did this and it went well.
- #17 - Because if you haven't got an elevator pitch for something you are doing, you probably don't understand it.
- My highlights:
- Be cautious of TDD.
- Theoretically great in pre-defined toy scenarios, but practically less useful than its proponents claim.
- Create tests, but don't religiously write all tests before all functional code.
- Be cautious of SOLID.
- Needs to be reframed and moderated by business and common sense to be useful.
- The "I" and "D" in particular are often cargo-culted into pointless ceremony.
- Be cautious of DRY.
- Premature abstraction is worse than duplication. Two things that look the same but evolve in different directions are not the same thing.
- Prefer the "rule of three": wait until you've duplicated something three times before extracting it.
- Types are documentation that can't lie. Use them. Lean into the type system.
asassertions are a small admission of defeat - sometimes necessary, never the default. - Boundaries matter. Be deliberate about what crosses module/service/network boundaries. The further data travels, the more validation it deserves.
- Observability before optimization. You cannot fix what you cannot see. Log, trace, and meter the things that matter before you need them.
- Idempotency is a superpower. Anything that can be retried safely will be. Anything that can't, won't be - and you'll find out at 2am.
- Boring technology is usually correct. Choose the boringest viable tool for the job.
- Build vs. buy: if it's not core to what you ship, prefer buy (or open source). Your differentiator should be where your effort goes.
- Standardize at the team level, not the codebase level. Linters, formatters, and import sorters are not where creativity should live.
- Most engineering problems are people problems under the hood.
- Write things down. Decisions, trade-offs considered, why the other options were rejected. Future you (and your colleagues) will be thankful.
- Disagree, then commit - but record the disagreement. If you were wrong, you learn. If you were right, the team learns.
- Reviewing code is teaching and learning at the same time. Treat it as such.
- Common engineering pathologies:
- Non-technical leaders.
- Normalization of deviance.
- Private equity financing.
- Usage/activity based OKRs.
- Poor handovers.
- Consensus mistaken for correctness.
- "We'll fix it later." - you won't.
- "It's only temporary." - it isn't.
- "It can't be fixed." - it can.