Chapter 7 — While You Are Coding

Topics 37–44 | Pages 324–402


Core Idea

Coding is not mechanical transcription of design. Every line involves decisions. This chapter covers the habits, disciplines, and practices that make the difference between code that merely runs and code that’s correct, maintainable, and safe.


Topic 37 — Listen to Your Lizard Brain (p327)

Tip 61: Listen to Your Inner Lizard

Experience deposits knowledge into your subconscious. When you feel uneasy about code — nervous, resistant, or like something is “too hard” — your lizard brain is trying to tell you something. It has no words, only feelings.

Fear of the blank page: Often signals either a real underlying concern the brain hasn’t consciously surfaced yet, or fear of making a mistake. Both have the same remedy.

Code pushing back: If coding feels like wading through mud, the code is telling you something is wrong — wrong structure, wrong problem, wrong approach.

How to talk lizard:

  1. Stop. Step away. Walk, eat, sleep — let ideas percolate without forcing them.
  2. Externalize: doodle, explain to a colleague or rubber duck.
  3. If still stuck: write “I’m prototyping” on a sticky note. Remind yourself that prototypes are meant to fail and be thrown away. Start with a one-sentence description of what you want to learn, then code. The permission to fail removes the block.

Topic 38 — Programming by Coincidence (p334)

Tip 62: Don’t Program by Coincidence

Programming by coincidence = relying on accidental success. Code works, but you don’t know why. When it breaks, you won’t know why either.

Sources of coincidence:

  • Accidents of implementation — relying on undocumented behavior, wrong call order that happens to work, calling routines the wrong way
  • Phantom patterns — seeing causal relationships that are actually correlation (the +1/-1 time zone story)
  • Accidents of context — assuming a GUI is present, assuming writable directories, assuming English locale, copy-pasting code without understanding whether the context matches

How to program deliberately:

  • Always know what your code does and why it works
  • If you can’t explain it to a junior developer, you may be relying on coincidence
  • Don’t code in the dark — understand the technology you’re using
  • Document assumptions; test them (use assertions)
  • Only rely on documented behavior; if you must rely on undocumented behavior, document that explicitly
  • Don’t be a slave to history — don’t let existing code constrain future code

Topic 39 — Algorithm Speed (p343)

Tip 63: Estimate the Order of Your Algorithms
Tip 64: Test Your Estimates

Big-O notation describes how resource usage grows as input size n grows:

NotationGrowthExamples
O(1)ConstantArray element access
O(log n)LogarithmicBinary search
O(n)LinearSequential search
O(n log n)Just above linearQuicksort (average), heapsort
O(n²)Square lawBubble sort, selection sort
O(n³)CubicMatrix multiplication
O(2ⁿ)ExponentialTraveling salesman, set partition

Common sense rules:

  • Simple loops → O(n)
  • Nested loops → O(n × m) or O(n²) for symmetric
  • Binary chop per iteration → O(log n)
  • Divide and conquer → O(n log n)
  • Permutations → factorial time (O(n!))

Practical advice:

  • When you see a loop, mentally check its order
  • If you have O(n²) and suspect n will be large, look for a divide-and-conquer approach
  • Fastest isn’t always best — for small n, simple O(n²) beats complex O(n log n) with high setup cost
  • Always test with real production-like data sizes; theoretical curves don’t account for cache effects, thrashing, etc.
  • Use profilers to count actual executions if timing is inaccurate

Topic 40 — Refactoring (p354)

Tip 65: Refactor Early, Refactor Often

Software is more like gardening than construction. Plants grow, get overgrown, need pruning. Code evolves; yesterday’s correct design is today’s obstacle.

Refactoring = restructuring existing code without changing external behavior. It’s a day-to-day activity, not a big-bang rewrite.

When to refactor:

  • DRY violations discovered
  • Non-orthogonal design found
  • Knowledge updated (requirements drifted, better understanding)
  • Real usage reveals different priorities
  • Performance requires moving functionality
  • A test just passed (great moment to tidy what you just wrote)

How to refactor safely (Fowler):

  1. Don’t refactor and add features simultaneously
  2. Ensure good tests before starting — run them constantly
  3. Take small, deliberate steps; test after each step

“Time pressure” excuses don’t hold up: every day you don’t refactor, the problem grows and takes longer to fix later. Treat technical debt like a growing tumor — remove it while small.


Topic 41 — Test to Code (p361)

Tip 66: Testing Is Not About Finding Bugs
Tip 67: A Test Is the First User of Your Code
Tip 68: Build End-to-End, Not Top-Down or Bottom Up
Tip 69: Design to Test
Tip 70: Test Your Software, or Your Users Will

The biggest benefit of testing is what happens when you think about tests, not when you run them. Thinking about how to test forces you to understand the code from the outside.

Before writing any code: imagine you have to test it. This reveals:

  • Global state that needs to be injected
  • Parameters that should be passed (not global)
  • Ambiguous requirements that need clarifying
  • Boundary conditions to handle

TDD trap: Some developers become slaves to the test-first cycle, writing redundant tests, chasing 100% coverage, and building bottom-up without a destination in mind. TDD is a tool, not a religion. You need to know where you’re going.

Testing against contract: for a function, test that preconditions reject bad input, postconditions hold for valid input, and boundary conditions are covered. Test subcomponents first; if their tests pass and the combined module fails, the problem is in the integration.

Test culture:

  • “Test later” = “Test never”
  • Tests that always fail get ignored, and that trains you to ignore all tests
  • Treat test code with the same care as production code
  • Don’t test volatile things (exact timestamps, widget positions, exact error message text) — fragile tests undermine the culture

Topic 42 — Property-Based Testing (p375)

Tip 71: Use Property-Based Tests to Validate Your Assumptions

Property-based testing: define invariants (properties that must always be true), let the framework generate random inputs to try to violate them.

The author of the code and the author of the tests share the same assumptions — property-based testing uses the computer (which shares none of your assumptions) to test.

Properties to look for: size invariants (sorting doesn’t change list length), ordering invariants (sorted list is monotone), conservation invariants (items taken + items remaining = original count).

When a property test fails: extract the failing inputs, create a regular unit test with those values. This fixes the case and creates a regression test (since property tests may generate different random values next time).

Property tests also improve design: thinking in invariants forces you to identify what must not change, removing edge cases and highlighting functions that leave state inconsistent.


Topic 43 — Stay Safe Out There (p385)

Tip 72: Keep It Simple and Minimize Attack Surfaces
Tip 73: Apply Security Patches Quickly

Security is not someone else’s problem. Most breaches aren’t from clever attackers — they’re from careless developers.

Five basic security principles:

  1. Minimize attack surface area — complexity creates attack vectors; simpler code is more secure. Never trust external input — sanitize before passing to databases, renderers, or shell commands. Unauthenticated APIs are attack vectors. Output can leak information (don’t reveal stack traces, internal paths, or password hints).

  2. Principle of least privilege — request only the permissions you need, for the shortest time. Cull old/unused accounts and services.

  3. Secure defaults — default settings should be the most secure option. Let users loosen security themselves if they choose.

  4. Encrypt sensitive data — personally identifiable info, credentials, financial data. Never check secrets, API keys, or SSH keys into version control.

  5. Maintain security updates — unpatched known vulnerabilities are the cause of the largest data breaches in history. Update everything — servers, laptops, phones, appliances.

Password antipatterns (all make you less secure): restricting length, truncating passwords, banning special characters, requiring regular changes, showing hints, disabling paste. Follow NIST guidelines instead.

On crypto: Never roll your own. Use well-vetted, maintained libraries. Outsource authentication to third-party providers where practical.


Topic 44 — Naming Things (p395)

Tip 74: Name Well; Rename When Needed

Names are everything. The brain processes words with high priority (Stroop effect). Misleading names are worse than meaningless ones — they actively misdirect.

Good naming rules:

  • Name things according to their role, not their type or implementation
  • Ask “what is my motivation to create this?” — if you can’t name it well, you may not have the right abstraction
  • Name what a function does for the caller’s intent, not its internal mechanism (applyDiscount not deductPercent)
  • Avoid over-generic names: usercustomer; amountdiscountPercentage
  • Use the domain vocabulary — names should match terms users and the business use

Honor the local culture (naming conventions differ by language/community). Maintain consistency across the team through a project glossary or shared vocabulary.

Renaming matters: when a name no longer reflects intent, fix it now. A name that’s wrong is a broken window (see Ch1). If you can’t rename easily, you have an ETC violation — fix that first.


Key Takeaways

  1. Your discomfort with code is data — listen to it and investigate before pushing through.
  2. Know why your code works; don’t rely on coincidence or undocumented behavior.
  3. Know your algorithm’s Big-O: simple loops are O(n), nested loops O(n²), binary chop O(log n).
  4. Refactor continuously — small, safe, test-backed steps; don’t wait for “a week to refactor.”
  5. Testing’s biggest value is in thinking about tests; it reveals design flaws, coupling, and unclear requirements.
  6. Property-based testing exposes assumptions you didn’t know you had.
  7. Security is foundational: minimize attack surface, least privilege, secure defaults, encrypt sensitive data, patch quickly.
  8. Names reveal intent — choose them carefully, rename when they drift from reality.