Clean Code in Go: Principles and Practical Examples
\n\nGo is a language that was designed from the start with an emphasis on simplicity and readability. However, even in such a concise language, you can write confusing and hard-to-maintain code. The concept of Clean Code helps developers create programs that are easy to read, test, and modify. In this article, we will break down the key principles of Clean Code as applied to Go and look at specific examples.
\n\nWhy is clean code important in Go?
\n\nGo is often used to create microservices, APIs, and high-load systems. In such projects, a team of developers works on the code. Clean code reduces cognitive load, speeds up code reviews, and reduces the number of bugs. The main goal is to write code so that it is understandable not only to the author but also to any other developer who sees it for the first time.
\n\n1. Naming Variables and Functions
\n\nProper naming is the foundation of clean code. In Go, it is common to use short names for local variables and longer names for global entities.
\n\nBad Example
\npackage main\n\nfunc f(x int, y string) int {\n z, _ := strconv.Atoi(y)\n return x + z\n}\n\n\nGood Example
\npackage main\n\nfunc addPriceToCount(count int, priceStr string) int {\n price, _ := strconv.Atoi(priceStr)\n return count + price\n}\n\n\nNaming rules in Go:
\n- \n
- Use
camelCasefor unexported (private) variables. \n - Use
PascalCasefor exported (public) functions and types. \n - Avoid single-letter names, except for loop indices (
i,j). \n - Do not include the variable type in its name (e.g.,
priceIntis bad,priceis good). \n
2. Package and File Structure
\n\nIn Go, each package should solve one task. Separate code by responsibility. A standard project structure looks like this:
\n\nmyproject/\n├── cmd/\n│ └── server/\n│ └── main.go\n├── internal/\n│ ├── handler/\n│ ├── service/\n│ └── repository/\n├── pkg/\n│ └── utils/\n└── go.mod\n\n\nPrinciple: The internal package protects code from external imports. The pkg package contains reusable utilities. The cmd directory stores entry points (main.go files).
Bad Example
\n// The "utils" package contains everything\npackage utils\n\nfunc Calculate(x, y int) int { ... }\nfunc SendEmail(to string) { ... }\nfunc ParseJSON(data []byte) { ... }\n\n\nGood Example
\n// The "calculator" package is only responsible for calculations\npackage calculator\n\nfunc Add(a, b int) int { return a + b }\n\n\n3. Error Handling in Go
\n\nGo has no exceptions — errors are returned as values. Clean code requires explicit and meaningful handling of each error.
\n\nBad Example
\nfunc readFile(path string) string {\n data, _ := os.ReadFile(path) // ignoring the error\n return string(data)\n}\n\n\nGood Example
\nfunc readFile(path string) (string, error) {\n data, err := os.ReadFile(path)\n if err != nil {\n return "", fmt.Errorf("read file %s: %w", path, err)\n }\n return string(data), nil\n}\n\n\nTips for error handling:
\n- \n
- Never ignore errors using
_. \n - Always add context:
fmt.Errorf("operation: %w", err). \n - Use custom error types for different situations. \n
- Do not use
panicfor ordinary errors — only for truly exceptional situations. \n
4. Functions: Length and Responsibility
\n\nA function