Foreword & Introduction
Small things matter.
- Know where things are. Naming is crucial.
- A piece of code should be where you expect to find it. If not, refactor to get it there.
- Don't litter your code with comments and commented-out code that "capture history or wished for the future".
- Standardization is important.
- Have the discipline to follow the practices. To frequently reflect upon your work — and be willing to change.
"In code, refactor mercilessly". Reminds me of a simple writing tip: Imagine that you are paid for each word that you remove, while still keeping the meaning. That's how you write well.
Craftsmanship is the way to write good code. How do we learn it? Knowledge and work. Gain the knowledge (principles, patterns, practices, heuristics) and grind that knowledge into yourself by working hard and practicing.
You could know it all, but without the hard work, you wouldn't be able to put it to practice. Practice and fail. And then try again.
You could skip the case studies in this book, in which case you've read another 'feel good' book about writing good code. Then you've learned nothing. But you can pay close attention to even the smallest details and follow along closely, then you'll learn quite a lot.
If you keep making a mess, it'll slow you down in the long run.
We read more code than we write, so we should make it easier to read — even if that makes it harder to write. But since we can't write it without reading it, we're actually making it easier to write as well. But this is over the long term.
We don't just write clean code. We have to maintain it as well. So keep it clean.
- Names should reveal intentions. If it requires a comment to explain, it's not a good name
- "Avoid Disinformation"
- Be very careful with similar names — it can be very misleading (breaking the above rule!)
- "Make Meaningful Distinctions" — different names for different things.
destination— much better names.
- "Noise words are redundant".
variableshould never appear in a variable name, don't put
stringin the name of a string, don't make two variables like
theThing. Distinguishing names is important.
- "Use Pronounceable Names"
- "Use Searchable Names"
- "Avoid Encodings" — never actually seen this before.
"Avoid Mental Mapping" — Just because you know what it means, doesn't mean that someone else will.
- Class names should be nouns or noun phrases, not verbs.
- Method names should be verbs or verb phrases. Accessors, mutators, and predicates should be prefixed with
- "Don't Be Cute" — Say what you mean. Jokes are only fun for you and those in on them.
- "Pick One Word per Concept" — If you write
retrieve, stick with it.
- "Don't Pun"
- "Use Solution Domain Names" — You can use technical terms
- "Use Problem Domain Names" — When there are no technical terms, use the name from the problem domain.
- "Add Meaningful Context" — If you see an address form, you might see 'state' as a variable. But if you see 'state' alone somewhere, would you assume that it's related to an address? Here, meaningful context could be naming it
- "Don't Add Gratuitous Context"
- Functions should be small.
- They should do only one thing.
- One Level of Abstraction per Function.
- Code should be able to read from the top to bottom. This is The Stepdown Rule.
- General rule for switch statements: "they can be tolerated if they appear only once, are used to create polymorphic objects, and are hidden behind an inheritance".
- Use descriptive names.
- The fewer arguments the better.
- They should have no side effects.
- They should either do something or answer something. Not both.
- Exceptions > Returning Error Codes
- Extract Try/Catch blocks into functions of their own.
- Error handling is one thing.
- Don't Repeat Yourself. The more duplicates, the more has to be changed when necessary. And more room for errors.
- You don't have to get all of this perfect in your first try. It is as writing. You can edit and refine.
- Only for when we can't express ourselves well enough with code.
- Comments don't make up for bad code.
- Explain yourself in code — comments should not be necessary.
Good comments (if necessary)
- Legal comments — copyright, authorship, etc.
- Informative comments
- Explanation of intent
- Warning of Consequences
- Amplification (of importance)
- Mumbling — Commenting because you 'should'
- Redundant comments — explaining code that explains itself
- Misleading comments — not accurate
- Mandated comments
- Journal comments — use source control...
- Noise comments — restate the obvious, provide no new information
- Scary noise — copy pasting and forgetting to change contents
- Functions or variables > comments
- Don't have commented-out code
- Don't have 'attribution comments'. Source control can do that.
- No systemwide information in the context of a local comment
- Don't put historical discussions or irrelevant descriptions of details into comments.
- Agree upon rules for formatting and be consistent in following them.
- Source files names should be simple but explanatory.
- Source files shouldn't be too big.
- Variables should be declared as close to their usage as possible.
- Instance variables should be declared at the top of the class.
- If a function calls another, they should be vertically close. The caller should be above the callee, if possible.
- In general, a function that is called should be below a function that does the calling.
- Short lines — you shouldn't have to scroll to the right.
- Indentation is important.
Objects and Data Structures
- We want to be free to change the type of variables or implementation on a whim.
- "Objects hide their data behind abstractions and expose functions that operate on that data"
- "Data structure expose their data and have no meaningful functions"
- The Law of Demeter: "a module should not know about the innards of the objects it manipulates"
- "Error handling is important, but if it obscures logic, it's wrong".
- Exceptions > return codes
- Don't return null. And don't pass it either.
- Having bad tests is worse or equal to having no tests.
- Readability makes tests clean. Clarity, simplicity, and density of expression. Say a lot with as few expressions as possible.
- One assert per test.
- A single concept per test.
Clean tests are F.I.R.S.T
- Fast, independent, repeatable, self-validating, and timely.
- Variables first. Then public functions. Private utilities thereafter.
- Variables and utility functions should be kept private, but that's not a strict rule.
- Classes should be small. It shouldn't have too many responsibilities.
- The name of a class should describe it's responsibilities.
- The Single Responsibility Principle = classes or modules should only have one responsibility — one reason to change.
- Classes should have a small number of instance variables.
- Each method should manipulate one or more of those variables. The more variables a method manipulates, the more cohesive it is to the class. We want this cohesion to be high.
Design is simple (according to Kent Beck) if it:
- "Runs all the tests"
- "Contains no duplication"
- "Expresses the intent of the programmer"
- "Minimizes the number of classes and methods"
- Concurrency helps us decouple what gets done from when it gets done.
- It always improves performance.
- Design doesn't change because of it.
- Understanding it is not important when working with a container.
- It incurs some overhead.
- "Complex concurrency is complex, even for simple problems."
- The bugs aren't usually repeatable.
- It requires a fundamental change in design strategy.
- Limit access to data that may be shared.
- Threads should be as independent as possible.
- Know your library. Know what's available to you.
Know your execution models.
- Dining Philosophers.
- To understand those execution models, you might want to know about Bound Resources, Mutual Exclusion, Starvation, Deadlock, and Livelock.