Skip to main content

97 Things Every Programmer Should Know: In Practice

· 5 min read
Max Kaido
Architect

In an industry obsessed with fleeting frameworks and hype-driven development, some wisdom is timeless. "97 Things Every Programmer Should Know" is a collection of such wisdom. It's not about a specific language or technology, but about the fundamental principles of crafting good software.

But principles are useless without practice. Here's how we're turning these lessons into tangible, enforceable rules in our monorepo.

The Complete List: 97 Things Every Programmer Should Know

This is the full table of contents from the book. It's a goldmine of experience from seasoned practitioners.

  1. Act with Prudence
  2. Apply Functional Programming Principles
  3. Ask, "What Would the User Do?" (You Are Not the User)
  4. Automate Your Coding Standard
  5. Beauty Is in Simplicity
  6. Before You Refactor
  7. Beware the Share
  8. The Boy Scout Rule
  9. Check Your Code First Before Looking to Blame Others
  10. Choose Your Tools with Care
  11. Code in the Language of the Domain
  12. Code Is Design
  13. Code Layout Matters
  14. Code Reviews
  15. Coding with Reason
  16. A Comment on Comments
  17. Comment Only What the Code Cannot Say
  18. Continuous Learning
  19. Convenience Is Not an -ility
  20. Deploy Early and Often
  21. Distinguish Business Exceptions from Technical
  22. Do Lots of Deliberate Practice
  23. Domain-Specific Languages
  24. Don't Be Afraid to Break Things
  25. Don't Be Cute with Your Test Data
  26. Don't Ignore That Error!
  27. Don't Just Learn the Language, Understand Its Culture
  28. Don't Nail Your Program into the Upright Position
  29. Don't Rely on "Magic Happens Here"
  30. Don't Repeat Yourself
  31. Don't Touch That Code!
  32. Encapsulate Behavior, Not Just State
  33. Floating-Point Numbers Aren't Real
  34. Fulfill Your Ambitions with Open Source
  35. The Golden Rule of API Design
  36. The Guru Myth
  37. Hard Work Does Not Pay Off
  38. How to Use a Bug Tracker
  39. Improve Code by Removing It
  40. Install Me
  41. Interprocess Communication Affects Application Response Time
  42. Keep the Build Clean
  43. Know How to Use Command-Line Tools
  44. Know Well More Than Two Programming Languages
  45. Know Your IDE
  46. Know Your Limits
  47. Know Your Next Commit
  48. Large Interconnected Data Belongs to a Database
  49. Learn Foreign Languages
  50. Learn to Estimate
  51. Learn to Say, "Hello, World"
  52. Let Your Project Speak for Itself
  53. The Linker Is Not a Magical Program
  54. The Longevity of Interim Solutions
  55. Make Interfaces Easy to Use Correctly and Hard to Use Incorrectly
  56. Make the Invisible More Visible
  57. Message Passing Leads to Better Scalability in Parallel Systems
  58. A Message to the Future
  59. Missing Opportunities for Polymorphism
  60. News of the Weird: Testers Are Your Friends
  61. One Binary
  62. Only the Code Tells the Truth
  63. Own (and Refactor) the Build
  64. Pair Program and Feel the Flow
  65. Prefer Domain-Specific Types to Primitive Types
  66. Prevent Errors
  67. The Professional Programmer
  68. Put Everything Under Version Control
  69. Put the Mouse Down and Step Away from the Keyboard
  70. Read Code
  71. Read the Humanities
  72. Reinvent the Wheel Often
  73. Resist the Temptation of the Singleton Pattern
  74. The Road to Performance Is Littered with Dirty Code Bombs
  75. Simplicity Comes from Reduction
  76. The Single Responsibility Principle
  77. Start from Yes
  78. Step Back and Automate, Automate, Automate
  79. Take Advantage of Code Analysis Tools
  80. Test for Required Behavior, Not Incidental Behavior
  81. Test Precisely and Concretely
  82. Test While You Sleep (and over Weekends)
  83. Testing Is the Engineering Rigor of Software Development
  84. Thinking in States
  85. Two Heads Are Often Better Than One
  86. Two Wrongs Can Make a Right (and Are Difficult to Fix)
  87. Ubuntu Coding for Your Friends
  88. The Unix Tools Are Your Friends
  89. Use the Right Algorithm and Data Structure
  90. Verbose Logging Will Disturb Your Sleep
  91. WET Dilutes Performance Bottlenecks
  92. When Programmers and Testers Collaborate
  93. Write Code As If You Had to Support It for the Rest of Your Life
  94. Write Small Functions Using Examples
  95. Write Tests for People
  96. You Gotta Care About the Code
  97. Your Customers Do Not Mean What They Say

Our Implementation: Enforcing Wisdom with Cursor Rules

Talk is cheap. Instead of just nodding along, we've codified the most critical principles as always-on Cursor rules. These aren't suggestions; they are part of our development environment.

Our Enforceable "97 Things" Ruleset:

  • #8 The Boy Scout Rule: boy-scout-rule
    • Use Case: Prevents the slow decay of code quality. Every commit must leave the code slightly better. This rule stops developers from saying "not my problem" and fosters collective ownership.
  • #11 Code in the Language of the Domain: code-in-domain-language
    • Use Case: The Mercury backend is filled with terms like Tournament, Atlas, and Dike. This rule forces us to use these terms consistently in our code, making it self-documenting and easier for new developers to understand the business logic without a translator.
  • #17 Comment Only What the Code Cannot Say: comment-only-what-code-cannot-say
    • Use Case: We've all seen // increment i by 1. This rule eliminates such noise and pushes for comments that explain the why, not the what. For example, a comment explaining why a specific delay is needed for a third-party API is valuable.
  • #26 Don't Ignore That Error!: dont-ignore-errors
    • Use Case: Prevents silent failures that cascade into 3 AM production fires. Every catch block must be meaningful. This rule would have flagged the empty error handling that allowed the Mercury tournament variant issue to go unnoticed until it hit production.
  • #30 Don't Repeat Yourself (DRY): dont-repeat-yourself
    • Use Case: We had similar validation logic for user permissions in both arcana and anytracker. A change in one wasn't reflected in the other, causing a security gap. This rule forces us to centralize such logic into shared libraries.
  • #55 Make Interfaces Easy to Use Correctly and Hard to Use Incorrectly: make-interfaces-hard-to-misuse
    • Use Case: Instead of passing primitive strings and numbers, we're pushed to create domain-specific types like TournamentId or PortfolioId. This leverages the TypeScript compiler to prevent us from passing a PortfolioId where a TournamentId is expected.
  • #76 The Single Responsibility Principle: single-responsibility
    • Use Case: A class or function should do one thing. The original TournamentConfigFactory in Mercury was responsible for creating configs, fetching latest versions, and applying experimental logic. This rule forces us to break such god objects into smaller, more maintainable pieces.

Case Study: The Mercury Tournament Variant Incident

The recent production failure in Mercury's R instance is a perfect case study for why these rules are not academic.

  • Don't Ignore That Error! was violated: The system that created TournamentEntity instances didn't throw an error when the variant was missing, leading to a silent failure.
  • Single Responsibility Principle was violated: The TournamentConfigFactory had too many responsibilities, hiding the fact that getLatestConfig() returned a base config without the necessary experimental block.
  • Make Interfaces Easy to Use Correctly... was violated: The variant property on TournamentEntity was optional in the code (TournamentVariant | undefined) but NOT NULL in the database. A stricter interface would have caught this at compile time.
  • Code in the Language of the Domain was violated: The inconsistent use of "config", "variant", and "experimental" created confusion about what was truly required to launch a tournament.

Had these rules been in place and enforced, this entire class of problem would have been impossible.

The Unenforceable (But Equally Important) Lessons

Some principles can't be linted. They are about mindset and team culture.

  • #18 Continuous Learning: Our tech stack (NestJS, FastAPI, React, BullMQ, etc.) is constantly evolving. Staying current is not optional.
  • #64 Pair Program and Feel the Flow: For complex domains like Mercury's risk engine (Dike), two heads are essential to avoid mistakes.
  • #70 Read Code: Before starting any new feature in a domain, we expect developers to read the existing code and its FDD.md to understand the context.

Conclusion

The "97 Things" book is more than a collection of tips; it's a blueprint for professional software development. By embedding its core lessons directly into our workflow as enforceable Cursor rules, we are moving from "knowing" to "doing". It's a pragmatic approach to ensuring that the wisdom of the past directly prevents the failures of the future.