@DanLebrero.

software, simply

Book notes: Code that fits in your head: Heuristics for Software Engineering

Book notes on "Code that fits in your head" by Mark Seemann

These are my notes on Code that fits in your head: Heuristics for Software Engineering by Mark Seemann.

Good advise for junior and mid level devs that have to work with Object-Oriented languages.

But my favourite quote:

Do yourself a favour and learn functional programming. It fits better in your head.

Key Insights

  • Only unsuccessful software ends.

    The act of describing a program in unambiguous detail and the act of programming are one and the same Kevlin Henney
  • SW engineering was derailed by personal computers:
    • They created a generation of self-taught programmers that grow unaware of existing SW engineering knowledge.
    • SW engineering should make the SW dev process more regular.
  • Change compiler and linter warnings into errors.
  • High-level tests should go easy on assertions.
  • Don’t get derailed: write down your improvement ideas and move on.
  • Explicit is better than implicit.
  • Cyclomatic complexity is one of the rare code metrics that I find useful in practice.
  • You can compose code in many ways, but there are more wrong ways than good ways.
  • Good interface design considers not only what is possible, but also what should be deliberately impossible.
  • Every time your code successfully builds, commit it.
  • Code reviews that take more than one hour are not effective.
  • Do not sit with the author to do a code review:
    • Author will influence the reviewer.
  • The more you edit test code, the less you can trust it.
  • Write a test to reproduce the bug.
  • Time it takes to execute a test suite matters: Less than 10 seconds.
  • Do yourself a favour and learn functional programming. It fits better in your head.
  • What to log:
    • Log all impure actions but not more.
  • Meetings don’t scale, documentation does.
  • Security is like insurance.

TOC

Part I: Acceleration

Chapter 1: Art or Science?

  • How we think about SW development shapes how we work.
  • Only unsuccessful software ends.

The act of describing a program in unambiguous detail and the act of programming are one and the same Kevlin Henney

  • None of the metaphors fit:
    • House building:
      • Projects.
      • Phases.
      • Dependencies.
    • Garden:
      • Who grows the garden?
    • Craftsman:
      • Heuristics: It does not scale.
    • SW engineering:
      • It was derailed by personal computers:
        • They created a generation of self-taught programmers that grow unaware of existing SW engineering knowledge.
  • The skills you need to work as a professional SW dev tend to be situational (codebase, tools, …).

Programming is Pop Culture Alan Kay

Chapter 2: Checklists

  • To enable, support and liberate.
    • Not to monitor or audit.
  • Improve the outcome with no increase in skill.
  • Change compiler and linter warnings into errors.
    • On existing codebases, enable one rule/namespace at a time.
  • You are the technical expert, it is your job to make technical decisions, which includes internal quality decisions.

Chapter 3 - Tackling Complexity

  • What does fit in your head.
  • SW engineering should make the SW dev process more regular.
  • Sustainability: balance between too much focus vs too little on value.
  • Why is programming difficult?
    1. Short-memory con hold only 4-7 pieces of information.
    2. You spend more time reading than writing code:
    • Optimise for code readability.
    • Every minute you invest in making the code easier to understand pays itself back tenfold.
    1. System 1 (making decision based on immediate information) vs System 2.
  • You can do SW engineering without understanding Computer Science.

Chapter 4 - Vertical Slice

  • SW eng: methodology to make sure that SW works as intended, and that stays that way.
    • As intended != solving the user problem.
  • Implement features with the simples possible code, but look out for duplication.
  • Walking skeleton.
  • Drive changes through tests.
  • Author ignores compiler warning when writing tests because readability, but does not for production code.
  • High-level tests should go easy on assertions:
    • Make only the essentials visible.
  • Author moves to unit tests with all behaviours test in green (???).
  • DTO, Repository, Entity, equals & hashcode. Very glad I left all of this behind.
  • Manual testing from time to time is ok.

Chapter 5 - Encapsulation

  • Trading a compile-time error for a runtime exception is a poor trade-off.
  • Encapsulation is a contract that describes valid interactions between objects and callers.
  • Red-green-refactor.
  • Postel’s Law: Be conservative in what you send, be liberal in what you accept.
  • With immutable objects, you only need to consider validity in one place: the constructor.

Chapter 6 - Triangulation

  • When you work with legacy code, you slowly, painstakingly commit the structure of the code base to long-term memory:
    • Information in long-term memory is harder to change, hence legacy code is harder to change.
  • Don’t get derailed: write down your improvement ideas and move on.
  • Explicit is better than implicit.
  • Devil’s advocate technique: try to pass test with an obviously incomplete implementation:
    • This works as a critique of your tests.
    • When to stop? Ask “How likely is such a regression to happen?”
  • Use only < or <=.

Chapter 7 - Decomposition

  • Once the team’s mindset has changed, the rule itself becomes redundant.
  • Cyclomatic complexity is one of the rare code metrics that I find useful in practice.
  • No more than 7 things should be going on in a single piece of code:
    • Each branch as one thing, so no more than cyclomatic complexity of 7.
  • “Static” methods or methods with 0 parameters are a code smell.
  • Parse, don’t validate:
    • Instead of returning a bool, return either an error or a class from the domain model.
  • Fractal architecture:
    • Each layer/piece of code should have no more than 7 things, those things themselves being composed of no more than 7 things.
    • Low level details should be represented as a single abstract chunk, and higher level details should be either irrelevant at that level of zoom, or otherwise explicitly visible as method parameters or injected dependencies.
  • Count variables to measure complexity:
    • Params + locals + class fields.
    • Consider refactoring to Parameter Object if complexity becomes high.
  • “If you know ASP.NET …”: In my experience with Spring, this is a big “if”. Spring has become a beast able to do many things, so knowing a lot of complex machinery beforehand should count somehow towards the complexity of the codebase.

Chapter 8 - API Design

  • You can compose code in many ways, but there are more wrong ways than good ways:
    • It requires skill and taste.
  • Good interface design considers not only what is possible, but also what should be deliberately impossible.
    • Design APIs so that it is difficult to misuse them.
  • Favour specialised APIs over Swiss Army API.
  • Remove method names from an interface and see if you can guess what they do out of the interface name + inputs + outputs.
  • Methods with side effects should return no data (void), so makes it trivial to recognise them:
    • Does this mean throw exceptions to signal errors?
  • From best to worse communication options:
    1. Types as they are check at compile time.
    2. Method names.
    3. Comments.
    4. Automated tests.
    5. Commit messages.
    6. Documentation.

Chapter 9 - Teamwork

  • Git: 50/72 rule
    • Headline in imperative 50 chars.
    • Body 72 chars wide.
  • CI: integrate every 4 hours.
  • Every time your code successfully builds, commit it.
  • Reject big PR.
  • Code reviews that take more than one hour are not effective.
  • Code review question: Will I be okay maintaining this?
  • Do not sit with the author to do a code review:
    • Author will influence the reviewer.
  • Cheer when you see something you like.

Part II - Sustainability

Chapter 10 - Augmenting Code

  • Strangler pattern at the method and class level.

Chapter 11 - Editing Unit Tests

  • The more you edit test code, the less you can trust it.
  • You can’t refactor unit tests.
  • A test method having only one assertion: too simplistic.
  • When you need to change your test code, try to do it without touching the production code.
  • Don’t trust a test that you haven’t seen fail:
    • Comment out production code and bring it back a piece at a time.

Chapter 12 - Troubleshooting

  • Step 1: Try to understand what is going on:
    • Use the scientific method:
      1. Make a prediction (hypothesis).
      2. Perform an experiment.
      3. Compare outcome with prediction.
      4. Goto 1 until you understand.
  • Delete code to simplify.
  • Time-box the process and take breaks if no progress is made.
  • Rubber ducking: the mere act of explaining a problem tends to produce new insights.
  • When a bug appears, stop what you are doing and fix it.
  • Write a test to reproduce the bug.
  • Time it takes to execute a test suite matters: Less than 10 seconds.

Chapter 13 - Separation of Concerns

  • You must be able to recompose what you decomposed.
  • Nested composition:
    • Typical in OO
    • Around side-effects:
      • You model actions.
      • Compose by nesting.
      • Hidden interactions: increases complexity.
  • Sequential composition:
    • Pipeline.
    • No side-effects.
    • Good, pure functions.
  • Push side-effects to the edge of the system.
  • Functional programming fits better in your head.
  • If you see an exception in the logs, treat it as a defect.
  • What to log:
    • HTTP request + response.
    • DB request + response.
    • Log all impure actions but not more.

Chapter 14 - Rhythm

  • Personal:
    • Pomodoro: time-box 25 mins, 5 min break even when in the flow.
    • Don’t work long hours.
    • Meetings don’t scale, documentation does.
    • Learn to touch type.
  • Team:
    • Update dependencies regularly.

Chapter 15 - The Usual Suspects

  • Security is like insurance.
  • STRIDE:
    • Spoofing: try to pose as somebody else.
    • Tampering.
    • Repudiation: deny performing an action, like receiving an item.
    • Information disclosure.
    • Denial of service.
    • Elevation of privilege.
  • Property base testing.
  • Behavioural code analysis

Chapter 16 - Tour

  • File organization: put all files in one directory:
    • Break into multiple packages to enforce no cyclic dependencies.
  • Learn a new system from tests.

Did you enjoy it? or share!

Tagged in : Architecture book notes