← Назад к вопросам

Расскажи про неудачные решения с точки зрения архитектуры

1.6 Junior🔥 231 комментариев
#Soft Skills и карьера#Микросервисы и архитектура

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Архитектурные ошибки в Go проектах

Опыт работы с Go показывает, что многие архитектурные проблемы возникают из-за нарушения ключевых принципов языка — простоты, явности и эффективности. Вот основные категории неудачных архитектурных решений:

1. Чрезмерное использование интерфейсов и абстракций

Go — язык практический, его философия избегает абстракций без явной необходимости. Частая ошибка — создание сложных слоев интерфейсов заранее, без реальной потребности.

// Плохо: интерфейс создан без необходимости
type UserServiceInterface interface {
    GetUser(id int) (*User, error)
    CreateUser(user *User) error
    UpdateUser(user *User) error
    DeleteUser(id int) error
}

// Хорошо: интерфейс появляется только при необходимости
type UserRepository struct {
    db *sql.DB
}

func (r *UserRepository) GetUser(id int) (*User, error) {
    // прямая реализация
}

Интерфейсы в Go должны появляться естественно — когда нужна настоящая полимофная поведение или мокирование в тестах. Создание интерфейсов "на будущее" приводит к:

  • Избыточной сложности
  • Размытию ответственности
  • Проблемам с тестированием (mock overuse)

2. Нарушение принципа единственной ответственности (SRP)

Go структуры и функции должны быть маленькими и понятными. Частая ошибка — создание "божественных объектов" (God Objects), которые делают слишком много.

// Плохо: сервис делает всё
type OrderService struct {
    db *sql.DB
    emailClient *EmailClient
    paymentProcessor *PaymentProcessor
}

func (s *OrderService) ProcessOrder(order *Order) error {
    // проверка заказа
    // сохранение в БД
    // отправка email
    // обработка платежа
    // логирование
    // ... 10+ операций
}

// Хорошо: разделение на специализированные компоненты
type OrderValidator struct {}
type OrderRepository struct {}
type NotificationService struct {}
type PaymentService struct {}

// Координация через отдельный компонент
type OrderProcessor struct {
    validator *OrderValidator
    repo *OrderRepository
    // ... только необходимые зависимости
}

Признаки нарушения SRP:

  • Функции с 50+ строк кода
  • Структуры с 10+ методами
  • Смешение логики разных доменов (например, бизнес-логика и инфраструктура)

3. Игнорирование управления ошибками как архитектурной проблемы

Обработка ошибок в Go — это архитектурный выбор. Неудачные решения:

  • Паника (panic) в бизнес-логике
  • Глобальные обработчики ошибок
  • Возврат ошибок без контекста
// Плохо: паника в нормальной операции
func ProcessRequest(req *Request) {
    if req.ID == 0 {
        panic("invalid request") // непредсказуемое поведение
    }
}

// Плохо: ошибка без контекста
func FindUser(id int) (*User, error) {
    if id <= 0 {
        return nil, errors.New("invalid id") // что значит invalid?
    }
}

// Хорошо: детализированные ошибки с контекстом
func FindUser(id int) (*User, error) {
    if id <= 0 {
        return nil, fmt.Errorf("user id must be positive: %d", id)
    }
    // ...
}

Архитектурно правильная обработка ошибок включает:

  • Определение стратегии (возврат, logging, graceful shutdown)
  • Использование typed errors для разных категорий
  • Централизованное логирование ошибок с контекстом

4. Плохое управление зависимостями и конфигурацией

Внедрение зависимостей в Go часто делается неправильно:

  • Глобальные переменные для конфигурации или зависимостей
  • Скрытые зависимости через init() функции
  • Жесткое связывание компонентов
// Плохо: глобальная конфигурация
var Config *AppConfig

func init() {
    Config = LoadConfig() // непредсказуемое время инициации
}

// Плохо: жесткая связь
type UserService struct {
    db *sql.DB // конкретная реализация, нельзя заменять
}

// Хорошо: явные зависимости через интерфейсы
type UserService struct {
    db Database // интерфейс
}

type Database interface {
    Query(string, ...interface{}) (*sql.Rows, error)
}

// Конфигурация передается явно
func NewUserService(db Database, config Config) *UserService {
    return &UserService{db: db}
}

5. Неэффективная организация пакетов (package layout)

Пакеты в Go должны отражать логическую структуру, а не технические слои. Ошибки:

  • Пакеты типа utils, common, helpers (становятся "мусорными")
  • Смешение публичных и внутренних API
  • Циклические зависимости между пакетами
# Плохая структура:
project/
├── controllers/
├── models/
├── utils/          # всё смешано
├── helpers/
├── repositories/

# Хорошая структура (по доменам):
project/
├── user/
│   ├── repository.go
│   ├── service.go
│   ├── handler.go
├── order/
│   ├── repository.go
│   ├── validation.go
├── payment/
│   ├── processor.go

Принципы хорошей организации пакетов:

  • Один пакет = одна логическая ответственность
  • Минимизация публичного API (больше internal)
  • Иерархия отражает бизнес-домены, не технические слои

6. Отказ от использования контекста (context.Context)

Context — ключевой механизм Go для управления жизненным циклом. Архитектурные ошибки:

  • Игнорирование контекста в API
  • Передача контекста без соблюдения границ
  • Создание собственных механизмов вместо использования context
// Плохо: API без поддержки контекста
func FetchData(url string) ([]byte, error) {
    // нет контроля времени выполнения
    return http.Get(url)
}

// Хорошо: контекст для контроля и метаданных
func FetchData(ctx context.Context, url string) ([]byte, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    // возможность cancellation, timeout
}

7. Смешение уровней абстракции в одном компоненте

Go ценит ясность и прямое соответствие. Частая ошибка — смешение инфраструктурного кода (DB, HTTP) с бизнес-логикой.

// Плохо: бизнес-логика смешана с инфраструктурой
func SaveOrder(order *Order) error {
    // бизнес-правила
    if order.Total < 0 {
        return errors.New("invalid total")
    }
    
    // инфраструктура (SQL)
    query := "INSERT INTO orders ..."
    _, err := db.Exec(query, order.ID, order.Total)
    
    // ещё бизнес-логика
    if order.NotifyCustomer {
        sendEmail(order.CustomerEmail)
    }
    
    return err
}

// Хорошо: разделение слоев
type OrderValidator struct {} // только бизнес-правила
type OrderRepository struct {} // только инфраструктура (DB)
type OrderNotifier struct {} // только коммуникации

Ключевые уроки для архитектуры Go:

  1. Сначала простота — добавляйте сложность только когда она необходима
  2. Явность выше абстракции — прямые реализации часто лучше интерфейсов
  3. Композиция вместо наследования — Go построен на композиции
  4. Ошибки как часть дизайна — планируйте обработку ошибок с начала
  5. Пакеты как границы модулей — используйте пакеты для логического разделения

Эти принципы помогают создавать поддерживаемые, эффективные и понятные системы на Go, которые хорошо масштабируются и адаптируются к изменениям требований.

Расскажи про неудачные решения с точки зрения архитектуры | PrepBro