Clean Code на Go: принципы написания читаемого и поддерживаемого кода
Чистый код — это не роскошь, а необходимость для любого серьёзного проекта. В языке Go, который славится своей простотой и лаконичностью, принципы Clean Code особенно важны. Плохо написанный код на Go быстро становится нечитаемым и трудным для поддержки, несмотря на все преимущества языка. В этой статье мы разберём основные принципы написания чистого кода на Go с конкретными примерами.
1. Правильное именование: основа читаемости
В Go действует правило: чем меньше область видимости, тем короче имя. Однако это не означает, что нужно использовать однобуквенные переменные везде. Рассмотрим примеры.
Плохо (нечитаемый код):
func f(x int, y int) int { z := x + y return z}Хорошо (чистый код):
func Sum(a int, b int) int { result := a + b return result}Важные правила именования в Go:
- Переменные: используйте camelCase (например,
userName,totalCount) - Экспортируемые (публичные) функции и типы: с большой буквы —
func CalculateTax() - Неэкспортируемые (приватные): с маленькой буквы —
func parseInput() - Аббревиатуры: пишите заглавными буквами (например,
HTTPHandler, а неHttpHandler) - Избегайте венгерской нотации: не пишите
strNameилиintCount— тип и так понятен
2. Обработка ошибок: явная и лаконичная
Go не использует исключения — ошибки возвращаются как значения. Чистый код требует явной обработки каждой ошибки. Не игнорируйте их!
Плохо (игнорирование ошибки):
file, _ := os.Open("data.txt")// Ошибка потеряна!Хорошо (явная обработка):
file, err := os.Open("data.txt")if err != nil { log.Fatalf("Не удалось открыть файл: %v", err)}defer file.Close()Дополнительные советы:
- Всегда проверяйте
err != nilсразу после вызова функции - Используйте
deferдля закрытия ресурсов - Создавайте собственные типы ошибок с помощью
fmt.Errorf("контекст: %w", err) - Не используйте панику (
panic) для обычных ошибок — это исключительная ситуация
3. Функции: короткие и с одной ответственностью
Принцип единственной ответственности (Single Responsibility Principle) применим и к функциям. Каждая функция должна делать только одно дело.
Плохо (функция делает слишком много):
func ProcessUserData(userID int) (string, error) { // 1. Подключение к БД db, _ := sql.Open("postgres", "...") // 2. Запрос данных row := db.QueryRow("SELECT name, email FROM users WHERE id = ?", userID) // 3. Форматирование var name, email string row.Scan(&name, &email) result := fmt.Sprintf("Имя: %s, Email: %s", name, email) // 4. Отправка email (побочный эффект!) sendEmail(email, "Приветствие") return result, nil}Хорошо (разделение на маленькие функции):
func GetUserName(db *sql.DB, userID int) (string, error) { var name string err := db.QueryRow("SELECT name FROM users WHERE id = ?", userID).Scan(&name) return name, err}
func FormatUserInfo(name, email string) string { return fmt.Sprintf("Имя: %s, Email: %s", name, email)}
func ProcessUserData(db *sql.DB, userID int) (string, error) { name, err := GetUserName(db, userID) if err != nil { return "", fmt.Errorf("ошибка получения пользователя: %w", err) } email, _ := getUserEmail(db, userID) /