Duplication (inadvertent or purposeful duplication) can lead to maintenance nightmares, poor factoring, and logical contradictions.
Implement things when you actually need them, never when you just foresee that you need them.
You will either need zero of a thing, one of a thing, or an arbitrary number of the thing. Don't place arbitrary limits on objects.
An interface is more useful when it's easier to satisfy it.
"Who cares? Shut up"
You can make your programs compile faster, easier to maintain, and simpler if you keep the dependency tree small.
"Y'know what? I don't need that whole library. All I need are these three lines of code, and I can just copy, and it's fine."
"You should be thinking about making the simple clear code, rather than trying to make cleverst densest stuff you can."
"A big part of all programming is how you handle errors. And people are too quick to jump to just return error up the tree and forget about it, rather than thinking about how an error should really work."
Don't focus comments on what your code is doing, focus on why.
"Think of the user when you write documentation. Don't be afraid to explain things so that they make sense."
Don't use panic for normal error handling. Use error and multiple return values.
Only fail when it's impossible to recover. Give clients errors, and let them try to recover.
A class should have only a single responsibility (i.e. only one potential change in the software's specification should be able to affect the specification of the class).
A class should be open for extension, but closed for modification.
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
No client should be forced to depend on methods it does not use. Many client-specific interfaces are better than one general-purpose interface.
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
Go wiki's CodeReviewComments
Run gofmt on your code to automatically fix the majority of mechanical style issues.
Comments documenting declarations should be full sentences, even if that seems a little redundant. Comments should begin with the name of the thing being described and end in a period.
All top-level, exported names should have doc comments, as should non-trivial unexported type or function declarations.
Error strings should not be capitalized (unless beginning with proper nouns or acronyms) or end with punctuation, since they are usually printed following other context.
Do not discard errors. If a function returns an error, check it to make sure the function succeeded. Handle the error, return it, or, in truly exceptional situations, panic.
Choosing whether to use a value or pointer receiver on methods can be difficult, especially to new Go programmers. If in doubt, use a pointer, but there are times when a value receiver makes sense, usually for reasons of efficiency, such as for small unchanging structs or values of basic type. Some useful guidelines:
- If the method needs to mutate the receiver, the receiver must be a pointer.
- If the receiver is a large struct or array, a pointer receiver is more efficient. How large is large? Assume it's equivalent to passing all its elements as arguments to the method. If that feels too large, it's also too large for the receiver.
- If the receiver is a struct, array or slice and any of its elements is a pointer to something that might be mutating, prefer a pointer receiver, as it will make the intention more clear to the reader.
- Finally, when in doubt, use a pointer receiver.
Tests should fail with helpful messages saying what was wrong, with what inputs, what was actually got, and what was expected.
The further from its declaration that a name is used, the more descriptive the name must be.
Common variables such as loop indices and readers can be a single letter (i, r). More unusual things and global variables need more descriptive names.
Zed Shaw's Defensive Programming
The Creative Programmer's Mind-Set
Programming is a creative process, and fear kills creativity. With this mind-set, accidents are designed to make you unafraid of taking chances and looking like an idiot:
- I can't make a mistake.
- It doesn't matter what people think.
- Whatever my brain comes up with is going to be a great idea.
However, this mind-set should not be carried over into the dark-site of the creative mind-set: Destructive Programmer's Mind-Set. These are lies:
- It's possible to write perfect software.
- My brain tells me the truth, and it can't find any errors: I have therefore written perfect software.
- My code is who I am and people who criticize its perfection are critizing me.
The Defensive Programmer's Mind-Set
The defensive programmer basically hates your code and believes:
- Software has errors.
- You aren't your software, yet you're responsible for the errors.
- You can never remove the errors, only reduce their probability.
The Eigth Defensive Programmer Strategies
Never Trust Input
Never trust the data you're given and always validate it.
If an error is possible, no matter how probable, try to prevent it.
Fail Early and Openly
Fail early, and openly, stating what happened, where, and how to fix it.
Clearly state the pre-conditions, post-conditions, and invariants.
Prevention over Documentation
Don't do with documentation that which can be done with code of avoided completely.
Automate everything, especially testing.
Simplify and Clarify
Always simplify the code to the smallest, cleanest form that works without sacrificing safety.
Don't blindly follow or reject rules.
- Avoid complex flow constructs, such as goto and recursion.
- All loops must have fixed bounds. This prevents runaway code.
- Avoid heap memory allocation.
- Restrict functions to a single printed page.
- Use a minimum of two runtime assertions per function.
- Restrict the scope of data to the smallest possible.
- Check the return value of all non-void functions, or cast to void to indicate the return value is useless.
- Use the preprocessor sparingly.
- Limit pointer use to a single dereference, and do not use function pointers.
- Compile with all possible warnings active; all warnings should then be addressed before release of the software.