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.
- It was derailed by personal computers:
- House building:
- 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?
- Short-memory con hold only 4-7 pieces of information.
- 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.
- 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:
- Types as they are check at compile time.
- Method names.
- Comments.
- Automated tests.
- Commit messages.
- 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:
- Make a prediction (hypothesis).
- Perform an experiment.
- Compare outcome with prediction.
- Goto 1 until you understand.
- Use the scientific method:
- 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.