Thoughts and short notes (in go) after reading «Clean Code»
1. Clean Code
The gist: Clean code is more than just working code; it's code that other developers can easily read, understand, and modify.
// Not so clean
func p(a []int) {
// ...some cryptic code...
}
// Clean
func sortNumbers(numbers []int) {
// sorting logic...
}
2. Meaningful Names
The gist: Use descriptive and specific names that reveal your intention.
// Confusing
var d int // elapsed time in days
// Clear
var elapsedTimeInDays int
// Even clearer in context
func calculateElapsedTime(startDate, endDate time.Time) int {
return int(endDate.Sub(startDate).Hours() / 24)
}
3. Functions
The gist: Functions should do one thing, be small, and have no side effects.
// Too complex
func handleHttpRequest(r http.Request) {
// parsing, logging, and handling...
}
// Decomposed into focused functions
func parseRequest(r http.Request) RequestData {...}
func logRequest(data RequestData) {...}
func handleRequest(data RequestData) Response {...}
4. Comments
The gist: Good code mostly speaks for itself.
// Unnecessary
// Check if user is valid
if user.IsValid() {...}
// Clear code
if user.IsValid() {...} // No comment needed
5. Formatting
The gist: Consistent and thoughtful formatting makes your code easier to read and understand.
// Inconsistent
func foo() {
var x int
x=3
fmt.Println(x)
}
// Consistent
func formatNumber(number int) string {
formatted := fmt.Sprintf("%03d", number)
return formatted
}
6. Objects and Data Structures
The gist: Objects hide their data behind abstractions and expose functions to operate on that data. Data structures expose their data and have no significant behavior.
// Data structure
type Rectangle struct {
Width float64
Height float64
}
// Object-like behavior
type Shape interface {
Area() float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
7. Error Handling
The gist: Treat error handling as a primary concern, not an afterthought.
func connectDatabase(connectionString string) (*sql.DB, error) {
db, err := sql.Open("postgres", connectionString)
if err != nil {
return nil, fmt.Errorf("error connecting to database: %v", err)
}
// Additional connection verification...
return db, nil
}
8. Boundaries
The gist: Respect boundaries between different layers and services by keeping interchanges well-defined and clean.
// Interfacing with an external package
func readConfigFile(path string) (Config, error) {
var config Config
data, err := ioutil.ReadFile(path)
if err != nil {
return config, err
}
err = json.Unmarshal(data, &config)
return config, err
}
9. Unit Tests
The gist: Tests keep your code flexible, maintainable, and understandable.
func TestCalculateTotal(t *testing.T) {
total := calculateTotal([]int{10, 20, 30})
if total != 60 {
t.Errorf("Expected 60, got %d", total)
}
}
10. Classes (Structs and Interfaces in Go)
The gist: Keep your data structures small, focused, and with a clear purpose.
type Server interface {
Start()
Stop()
}
type HttpServer struct {
// Server configuration fields
}
func (h *HttpServer) Start() {
// start server
}
func (h *HttpServer) Stop() {
// stop server
}
11. Systems
The gist: Building clean systems involves organizing code into layers and managing dependencies carefully.
Conceptual Example:
Organize your code into packages with clear purposes. Use interfaces to abstract implementation details and dependencies.
12. Emergence
The gist: Simple design, refactoring, and a focus on building well-crafted code lead to emergent behavior.
Conceptual Example:
Regularly refactor and simplify your code. Remove duplication, improve names, and break large functions into smaller, more focused ones.
13. Concurrency
The gist: Concurrency is tricky; keep it separate and well-managed.
func processConcurrently(data []int) {
var wg sync.WaitGroup
for _, value := range data {
wg.Add(1)
go func(val int) {
defer wg.Done()
processValue(val)
}(value)
}
wg.Wait()
}
14. Successive Refinement
The gist: Code should be continuously improved upon.
Conceptual Example:
Take an existing function or module and improve it: clarify names, split large functions, reduce dependencies, and add tests.
15. JUnit Internals
The gist: Understanding how good frameworks and libraries work can inspire you to write better code.
Conceptual Example:
Look into the source code of a well-respected Go package to understand its structure and design decisions.
16. Refactoring SerialDate
The gist: Refactoring is an essential tool for maintaining and improving the structure of existing code.
Conceptual Example:
Identify a complex module or function in your codebase and step through a refactoring process to improve it.
17. Smells and Heuristics
The gist: Recognize common "smells" in your code that indicate problems and understand heuristics for solving them.
Conceptual Example:
Review your code for "smells" such as rigidity, needless complexity, and duplication. Apply heuristics like the Single Responsibility Principle and DRY (Don't Repeat Yourself) to improve it.
Conclusion
Applying clean code principles isn't just about following rules; it's about developing a mindset. The more you practice writing clear, concise, and maintainable code, the more natural it will become. Just making a program that works isn't enough; it should also be tidy and understandable. Remember, clean code benefits your future self and your teammates, so always aim for clarity and simplicity. Keep it tidy!