Author: Kevlin Henney Watched/Read: January 15, 2023
Chapter 1. Act with Prudence
Chapter 2. Apply Functional Programming Principles
Referential transparency - the property of a function to yield the same result, given the same input, regardless of where and when the function is invoked. That is, function evaluation depends less—ideally, not at all—on the side effects of mutable state.
Chapter 3. Ask, “What Would the User Do?” (You Are Not the User)
The best way to find out how a user thinks is to watch one. Ask a user to complete a task using a similar piece of software to what you’re developing. Make sure the task is a real one: “Add up a column of numbers” is OK; “Calculate your expenses for the last month” is better. Avoid tasks that are too specific, such as “Can you select these spreadsheet cells and enter a SUM formula below?”—there’s a big clue in that question. Get the user to talk through his or her progress. Don’t interrupt. Don’t try to help. Keep asking yourself, “Why is he doing that?” and “Why is she not doing that?”
You’ll see users getting stuck. When you get stuck, you look around. When users get stuck, they narrow their focus. It becomes harder for them to see solutions elsewhere on the screen. It’s one reason why help text is a poor solution to poor user interface design. If you must have instructions or help text, make sure to locate it right next to your problem areas. A user’s narrow focus of attention is why tool tips are more useful than help menus.
Users tend to muddle through. They’ll find a way that works and stick with it, no matter how convoluted. It’s better to provide one really obvious way of doing things than two or three shortcuts.
Chapter 4. Automate Your Coding Standard
Chapter 5. Beauty Is in Simplicity
Beauty of style and harmony and grace and good rhythm depends on simplicity.
- Plato
Chapter 6. Before You Refactor
Chapter 7. Beware the Share
Just because two separate pieces of code happen to perform some small piece of logic the same, doesn’t indicate that the duplication should be removed. Removing duplication by introducing a shared abstraction increases coupling, and if the things being coupled together lack cohesion, the system is now worse for it.
While I’d decreased the absolute number of lines of code in the system, I had increased the number of dependencies. The context of these dependencies is critical—had they been localized, the sharing may have been justified and had some positive value. When these dependencies aren’t held in check, their tendrils entangle the larger concerns of the system, even though the code itself looks just fine.
Duplication is far cheaper than the wrong abstraction.
- Sandi Metz
Chapter 8. The Boy Scout Rule
Chapter 9. Check Your Code First Before Looking to Blame Others
Chapter 10. Choose Your Tools with Care
Chapter 11. Code in the Language of the Domain
PICTURE TWO CODEBASES. In one, you come across:
if (portfolioIdsByTraderId.get(trader.getId())
.containsKey(portfolio.getId())) {...}You scratch your head, wondering what this code might be for. It seems to be getting an ID from a trader object; using that to get a map out of a, well, map-of-maps, apparently; and then seeing if another ID from a portfolio object exists in the inner map. You scratch your head some more. You look for the declaration of portfolioIdsByTraderId and discover this:
Map<int, Map<int, int>> portfolioIdsByTraderId;Gradually, you realize it might have something to do with whether a trader has access to a particular portfolio. And of course you will find the same lookup fragment—or, more likely, a similar but subtly different code fragment—whenever something cares whether a trader has access to a particular portfolio.
In the other codebase, you come across this:
if (trader.canView(portfolio)) {...}No head scratching. You don’t need to know how a trader knows. Perhaps there is one of these maps-of-maps tucked away somewhere inside. But that’s the trader’s business, not yours.
Now which of those codebases would you rather be working in?
Chapter 12. Code Is Design
Chapter 13. Code Layout Matters
Talks about automatic formatter for basics, and then “hand-finished” adjustments.
Unless there’s active dissension, a team will quickly converge on a common “hand-finished” style.
This is… not my experience. Anything that is not completely automated just ends up being a mishmash of whatever the authors had in their minds at the time of writing.
Better to take the automation as far as possible, and enforce it in build for consistency.
Chapter 14. Code Reviews
Instead of simply correcting mistakes in code, the purpose of code reviews should be to share knowledge and establish common coding guidelines. Sharing your code with other programmers enables collective code ownership. Let a random team member walk through the code with the rest of the team. Instead of looking for errors, you should review the code by trying to learn and understand it.
Strong agree. Non-blocking code reviews (e.g. code review is not a requirement for code to make it to production).
Chapter 15. Coding with Reason
Chapter 16. A Comment on Comments
Talks about commenting code with information on what the code is doing. Mostly disagree. Comment the “why” of code… there are very few situations where I would want to comment the “what” of code — just make the code better and get rid of the comment.
Chapter 17. Comment Only What the Code Cannot Say
Any shortfall between what you can express in code and what you would like to express in total becomes a plausible candidate for a useful comment. Comment what the code cannot say, not simply what it does not say.
Strong agree.
Chapter 18. Continuous Learning
Chapter 19. Convenience Is Not an -ility
On API design
This method was likely designed for the convenience of the implementer as opposed to the convenience of the caller—“I don’t want the caller to have to make two separate calls” translated into “I didn’t want to code up two separate methods.” There’s nothing fundamentally wrong with convenience if it’s intended to be the antidote to tediousness, clunkiness, or awkwardness. However, if we think a bit more carefully about it, the antidote to those symptoms is efficiency, consistency, and elegance, not necessarily convenience. APIs are supposed to hide underlying complexity, so we can realistically expect good API design to require some effort. A single large method could certainly be more convenient to write than a well-thought-out set of operations, but would it be easier to use?
A diverse vocabulary allows us to express subtleties in meaning. For example, we to say run instead of
walk(true)even though could be viewed as essentially the same operation, just executed at different speeds.
Next time you are tempted to lump a few together into one API method, remember that the English language does not have word for
MakeUpYourRoomBeQuietAndDoYourHomeWork, even though it would be really convenient for such a frequently requested operation.
Chapter 20. Deploy Early and Often
Chapter 21. Distinguish Business Exceptions from Technical
…for example, if I try to withdraw money from an account with insufficient funds). In other words, this kind of situation is a part of the contract, and throwing an exception is just an alternative return path that is part of the model and that the client should be aware of and be prepared to handle.
This seems like a candidate for designing away exceptions, as in A Philosophy of Software Design. I would argue that withdrawing money from an account with insufficient funds is not an exceptional scenario - it may not be the main case, but it is not exceptional. Perhaps we would just query the whether the amount can be withdrawn from said account, and if not return a zero amount…. not sure.
Mixing technical exceptions and business exceptions in the same hierarchy blurs the distinction and confuses the caller about what the method contract is, what conditions it is required to ensure before calling, and what situations it is supposed to handle. Separating the cases gives clarity and increases the chances that technical exceptions will be handled by some application framework, while the business domain exceptions actually are considered and handled by the client code.
Generally agree with this.
Chapter 23. Domain-Specific Languages
Chapter 24. Don’t Be Afraid to Break Things
Don’t be afraid of your code. Who cares if something gets temporarily broken while you move things around? A paralyzing fear of change is what got your project into this state to begin with. Investing the time to refactor will pay for itself several times over the lifecycle of your project. An added benefit is that your team’s experience dealing with the sick system makes you all experts in knowing how it should work. Apply this knowledge rather than resent it.
Chapter 25. Don’t Be Cute with Your Test Data
In summary, when writing any text in your code—whether comments, logging, dialogs, or test data—always ask yourself how it will look if it becomes public. It will save some red faces all around.
Chapter 26. Don’t Ignore That Error!
Handle errors and exceptions at the earliest possible time. Also related: designing away errors.
Chapter 27. Don’t Just Learn the Language, Understand Its Culture
Chapter 28. Don’t Nail Your Program into the Upright Position
Sometimes the program should just blow up. Don’t try to catch every exception. There should be no
try {
}
catch (Exception e) {
}to be seen.
Chapter 29. Don’t Rely on “Magic Happens Here”
I’ve seen projects lose weeks of developer time because no one understood how they relied on “the right” version of a DLL being loaded. When things started failing intermittently, team members looked everywhere else before someone noticed that “a wrong” version of the DLL was being loaded.
You don’t have to understand all the magic that makes your project work, but it doesn’t hurt to understand some of it—or to appreciate someone who understands the bits you don’t.
Chapter 30. Don’t Repeat Yourself
Repetition in Process Calls for Automation Wherever painful manual processes exist that can be automated, they should be automated and standardized. The goal is to ensure that there is only one way of accomplishing the task, and it is as painless as possible.
Strong agree.
Repetition in Logic Calls for Abstraction
Strong maybe. See…
Chapter 31. Don’t Touch That Code!
Chapter 32. Encapsulate Behavior, Not Just State
Basically just DDD
Chapter 33. Floating-Point Numbers Aren’t Real
Chapter 34. Fulfill Your Ambitions with Open Source
Chapter 35. The Golden Rule of API Design
It’s not enough to write tests for an API you develop; you have to write unit tests for code that uses your API.
When you follow this rule, you learn firsthand the hurdles that your users will have to overcome when they try to test their code independently.
Chapter 36. The Guru Myth
If someone seems like a guru, it’s because of years dedicated to learning and refining thought processes. A “guru” is simply a smart person with relentless curiosity.
Chapter 37. Hard Work Does Not Pay Off
To avoid wasted work, you must allow time to observe the effects of what you are doing, reflect on the things that you see, and change your behavior accordingly.
Professional programming is usually not like running hard for a few kilometers, where the goal can be seen at the end of a paved road. Most software projects are more like a long orienteering marathon. In the dark. With only a sketchy map as guidance. If you just set off in one direction, running as fast as you can, you might impress some, but you are not likely to succeed. You need to keep a sustainable pace, and you need to adjust the course when you learn more about where you are and where you are heading.
Chapter 38. How to Use a Bug Tracker
Chapter 39. Improve Code by Removing It
Code to the requirements as they are right now, don’t try to predict the future.
Chapter 40. Install Me
Chapter 41. Interprocess Communication Affects Application Response Time
Chapter 42. Keep the Build Clean
Fix build warning as they come up.
Chapter 43. Know How to Use Command-Line Tools
Chapter 44. Know Well More Than Two Programming Languages
Developers that only know one language, or one programming paradigm, will have their thinking constrained to that one language.
Chapter 45. Know Your IDE
Chapter 46. Know Your Limits
Chapter 47. Know Your Next Commit
Know your next commit. If you cannot finish, throw away your changes, then define a new task you believe in with the insights you have gained. Do speculative experimentation whenever needed, but do not let yourself slip into speculative mode without noticing. Do not commit guesswork into your repository.
Chapter 48. Large, Interconnected Data Belongs to a Database
Chapter 49. Learn Foreign Languages
Building software is a highly social endeavour. Being able to communicate with all different kinds of people, including non-technical stakeholders is important.
If you work in a particular domain, e.g. accounting or financial services, having an understanding of that domain and being able to converse with domain experts is similarly important.
Chapter 50. Learn to Estimate
Chapter 51. Learn to Say, “Hello, World”
Seems to be extolling the virtues of REPL-based programming. We might more often than not be working on code that exists within the context of a much bigger system. This doesn’t mean that we can’t extract bits of code to run in a REPL.
Chapter 52. Let Your Project Speak for Itself
Attach real physical devices like lamps or speakers to your code and build health.
Chapter 53. The Linker Is Not a Magical Program
Chapter 54. The Longevity of Interim Solutions
Chapter 55. Make Interfaces Easy to Use Correctly and Hard to Use Incorrectly
Chapter 56. Make the Invisible More Visible
If you’re 90% done and endlessly stuck trying to debug your way through the last 10%, then you’re not 90% done, are you? Fixing bugs is not making progress. You aren’t paid to debug. Debugging is waste. It’s good to make waste more visible so you can see it for what it is and start thinking about trying not to create it in the first place.