The Pragmatic Programmer Notes
Interpersonal Skills
- When something goes wrong, try to fix it right away. If you can’t, be honest and proactive about what happened with the right people. This maintains trust and accountability during uncertainty.
- Stone soup: selectively tailor the story to each stakeholder so they feel centered. Choose whether to sell the moonshot or the incremental benefit. Small visible actions for buy in. However beware the frog story: you can acclimate the frog to heat if your raise the temperature slowly, but your leadership and solutioning better be correct.
On Knowledge
- Be a tinkerer: try new ideas early and frequently, and let them go just as easily. Seek better, not evangelism.
- Confidence comes from experience.
- Attack the computer (ATP - energy). You learn and are productive when you’re confident to explore, test, and flowingly use tools.
- Knowledge portfolio like a financial portfolio:
- Invest regularly; time and even money. Holding and timing rarely works.
- Diversify (breadth in the T).
- Manage risk: choose deep dives carefully. Knowledge moats matter if there are real use cases. This is easier said than done. What are good heuristics? Should you dive deep on AI? I think becoming an expert in the emerging tech of your current company is a safe bet.
- Buy low, sell high. Always good to get in on tech before the hype.
- Review and rebalance as tech inevitably changes over career.
- Don’t be afraid of scholarly work.
- Analyze learnings/readings with the five whys and the rhetorical triangle (writer, audience, context).
Pragmatic Systems Thinking
- It is OK to duplicate code. Knowledge and intent should be DRY. Two validation functions may have similar code but be for orthogonal domains.
- Orthogonal components (modular, independent, single responsibility, cohesive) ideally yield M * N functionality plus easier maintenance and evolvability because of easy testing and isolated modification.
- Tracer bullets: A machine gun uses tracer rounds to track where bullets land. Similarly, develop incrementally (the bullets) while maintaining high quality and frequent user touchpoints (the tracers). Building and getting feedback fast uncovers “unknown unknowns” in the existing system and guides direction. If you miss or hit the wrong place, you adjust your approach. If you nail it, you move to the next target. You build atop your infrastructure and accumulated feedback. This method requires good infrastructure and practices, which is inherently beneficial.
Estimates
- The further out the estimate, the less certain you are. So, adjust days or weeks to months. Provide estimates for various scenarios.
- Revise timelines as you iterate.
Tools
- Woodworkers have tools. Some bought, some made. Over time, more tools become specialized purchases as toolmaking becomes its own craft. Need and tinkering drive new tool acquisition.
-
Why is this a big deal? Are we saying you’ll save lots of time? Actually yes: over the course of a year, you might actually gain an additional week if you make your editing just 4% more efficient and you edit for 20 hours a week.
- Also frees up mental bandwidth from editing mechanics.
- Go a week without a mouse. Write down what’s hard and how you solved it.
Debugging
- Read error messages closely (learn new HTTP codes encountered, understand implications of common runtime errors, etc.).
- Write a test to isolate the case; step through with a debugger. Watch stack frames and locals.
- Write notes with pen and paper.
- Binary chop/search debugging (stack frames, code releases, etc.).
- Walk the happy path out loud preferably with someone.
- The bug usually isn’t in the third‑party library. If it is, before you make a bug report prove your code isn’t at fault.
- Question your assumptions.
Pragmatic Paranoia
changed notes style to chapters and subchapters
Design by Contract
- No perfect software - life’s axiom.
- Defensive coding is like driving: assume mistakes by fellow coders and yourself (no exceptions).
- Design by contract: preconditions, postconditions, and invariants (a la Eiffel OOP lang). Great example in exercise 14. This is closer to Elixir guard clauses in function heads than user input validation which is usually not a bug. Types and comments add strictness as well.
-
If your contract indicates that you’ll accept anything and promise the world in return, then you’ve got a lot of code to write!
- FastAPI’s declarative runtime validation (decorators + type reification) feels like lightweight DBC at API boundaries; without it, some languages happily propagate invalid values (like NaN from sqrt in JS) instead of raising errors.
-
- Exercise 15: fencepost errors still get me.
Dead Programs Tell No Lies
- Always include a default in case/match/switch so you know when hit the “impossible” case.
- A dead program does less damage than a crippled one.
- No assumptions only checked invariants.
Assertive Programming
- Clarifies failure, fault, error. Fault and error often swapped.
- Heisenberg of debugging: asserts with side-effects change the system you’re trying to observe.
How to Balance Resources
- Deallocate in reverse order of allocation (AB then BA) to avoid orphans.
- Allocate resources in consistent order to prevent deadlocks.
- On deallocation, decide whether to recursively deallocate children, leave orphans, or refuse if children remain.
Don’t Outrun Your Headlights
Bend, Or Break
Decoupling
- Coupling is often transitive: change DB → change service → change callers.
- Reusability and coupling can be at odds. A shared helper for two functions reduces duplication but couples callers to its API. Suddenly one function needs extra features and you have to decide whether to abandon the helper or update the API, breaking the contract for the other.
- Global data, singletons, and mutable external resources are dangerous because can be unknowingly changed. Wrap access in an API (indirection layer). If the auth solution or DB is replaced, should still have the same internal data transfer objects.
-
- Method chaining isn’t bad, but we often chain internal details. Favor
tell, don’t askAPIs that return top‑level concepts. Avoidcustomer.orders.find(leaks aggregation), but also avoidcustomer.applyDiscountToOrderbecause orders should not be hidden inside customers because it is a domain with its own existence.
- Method chaining isn’t bad, but we often chain internal details. Favor
Juggling the Real World
- Observer pattern (Exps: register callbacks to UI events, event hooks)
-
- Pub/sub model advances observer by adding asychroncitiy and removing serial bottlenecks.
-
pubsub is a great example of reducing coupling by abstracting up through a shared interface (the channel)
Transforming Programming
- Code transforms inputs into outputs.
- Elixir pipes and stdlib encourage this mindset. Show how to use ok/error wrappers and
and_thento emulate at runtime set‑theoretic types and short‑circuiting primitives in transformation pipelines.
Inheritance Tax
- Inheritance forces coupling: subclasses rely on parent implementations. Just like multiple callers relying on a shared helper function. And have same duality of adjusting parent API or having one child diverge.
- If you can’t duck-type (structural), use interfaces (nominal) to define the contract.
has-atrumpsis-a.
Configuration
Concurrency
Breaking Temporal Coupling
- Activity diagram: shapes for actions, arrows for dependencies, horizontal lines for sync points.
Shared State Is Incorrect State
- Great Metaphor: two waiters see one pie left and both sell it. Only one customer gets it. Race condition.
- Locks/semaphores only work if everyone abides and uses them to make operations atomic.
- better to adjust the API so it’s atomic (e.g.,
get_pie_if_available), and keep the lock handling inside.
- better to adjust the API so it’s atomic (e.g.,
Actors and Processes
Blackboards
- I need to think more about the whiteboard/fridge analogy for blackboard systems.
- They cited Kafka being used as a blackboard system. In OLAP a data lake would be fitting.
- Core idea: asynchronous multi‑writer/reader/format communication. Anyone, anything.
While You Are Coding
Listen to Your Lizard Brain
-
Instincts make you feel, not think. And so when an instinct is triggered, you don’t see a flashing lightbulb with a banner wrapped around it. Instead, you get nervous, or queasy, or feel like this is just too much work.
- When you’re spinning, walk away, then externalize later (doodle, talk, etc.).
Programming by Coincidence
- Always know why the code works. That makes it easier to debug, change, or defend.
Don’t assume it, prove it.- Test your assumptions and your code (DBC).
Refactoring
- Refactoring is a subset of restructuring/rewriting. It means disciplined changes that don’t alter external behavior. Like upkeeping a garden, not demolishing half a building and rearchitecting.
Test to Code
- Testing makes you see your code as a client.
- Testing forces understanding of the problem that must be solved.
- Hubris of top‑down: thinking you know everything up front.
- Hubris of bottom‑up: thinking you can build infinitely flexible abstractions.
- Testing and hashing out contracts let you know what user wants and what good development is. Make the vertical slice to the best of your ability. Maybe as scope grows a horizontal slice will be built into a platform.
- If you ad‑hoc test with prints/console, add those cases to unit tests afterward.
Property Based Testing
- Instead of examples, test invariants across many inputs (exp: linearizable semantics). Often easier than formulating edge cases. PBT may lead to the unit test case.
Naming Things
- Stroop effect: words are powerful tools.
- Culture and conventions matter:
- One‑letter names can be great (i, j, k for loops; c/s for character/string).
- Consistency is most important.
- “User” is a bad name. Pick something with more context.
- Misleading names are a broken window like a failing test, leading to future errors and bad practices.
Before the Project
The Requirement Pit
- A requirement, whether in technical or business terms, is rarely thought through to sufficient LOD.
- Your job is to gently open Pandora’s box.
- To learn requirements, become or shadow the client.
- This has the sideeffect of building trust.
- Code is the best documentation (source of truth).
- Keep a project glossary with domain terminology for everyone, not just engineers.
- Requirements should be as high‑level as possible without losing needs.
Solving Impossible Puzzles
-
It’s not whether you think inside the box or outside the box. The problem lies in finding the box—identifying the real constraints.
— Nine dots puzzle - I like this, thinking outside the box is silly due to infinity. Defining the box and efficiently & effectively understanding it is the way.
Working Together
- Conway’s Law also stretches to include the user.
- Pair programming’s 2 primary benefits:
- Each participant can focus their cognitive load on a specific task. One approaches the problem from the code and another from a high-level scope. - peer - Peer pressure towards good practices (this is huge and rigorous code review does this to some extent).
- Mob program with non-devs.
The Essence of Agility
We are uncovering better ways of developing software by doing it and helping others do it. Through this work we have come to value:
- Individuals and interactions over processes and tools
- Working software over comprehensive documentation
- Customer collaboration over contract negotiation
- Responding to change over following a plan
- That is, while there is value in the items on the right, we value the items on the left more.
— Agile Manifesto
- Agile falls apart without good design because frequent change becomes overwhelming.
Pragmatic Projects
Pragmatic Teams
-
Too many teams are so busy bailing out water that they don’t have time to fix the leak.
- Learning as a team beats solo learning. Make it part of the schedule (even a team member talking about their approach in some code).
- Create a team brand/identity. Use it liberally.
Pride and Prejudice
- Own your code and expertise proudly. Communal ownership is often all the more powerful.
- The golden rule applies to other people’s code.