Ответ
Хорошая архитектура нацелена на создание поддерживаемого, тестируемого и расширяемого кода. Вот несколько распространенных анти-паттернов в Go и способы их решения:
1. Глобальные переменные и неявное состояние
Усложняют тестирование, приводят к неочевидным зависимостям и состоянию гонки в конкурентной среде.
Плохо:
var db *sql.DB // Глобальная переменная
func GetUser(id int) (*User, error) {
// Функция неявно зависит от глобальной переменной db
row := db.QueryRow("SELECT ...")
// ...
}
Как лучше:
Использовать Dependency Injection (DI). Передавать зависимости явно через параметры конструктора или функции.
type UserRepo struct {
db *sql.DB
}
func (r *UserRepo) GetUser(id int) (*User, error) {
row := r.db.QueryRow("SELECT ...")
// ...
}
2. Жесткая связность компонентов (Tight Coupling)
Модули знают о конкретных реализациях друг друга, что усложняет замену компонентов и тестирование.
Плохо:
type OrderService struct {
// Привязка к конкретной реализации MySQL
storage *MySQLStorage
}
Как лучше:
Следовать Принципу инверсии зависимостей (DIP). Зависеть от абстракций (интерфейсов), а не от конкретных типов.
type OrderStorage interface {
Save(Order) error
}
type OrderService struct {
storage OrderStorage // Зависимость от интерфейса
}
3. Нарушение принципа единственной ответственности (SRP)
Структура или пакет делают слишком много. Например, один модуль отвечает за логику, работу с БД и форматирование ответа.
Плохо:
// Генератор отчётов знает и о БД, и о форматах вывода
type ReportGenerator struct {
db *sql.DB
pdfGen *PDFGenerator
csvGen *CSVGenerator
}
Как лучше:
Разделять обязанности. Один компонент получает данные, другой — форматирует их.
4. Чрезмерное использование interface{}
Использование пустого интерфейса (any
) там, где можно использовать конкретный тип или более строгий интерфейс, приводит к потере типобезопасности и усложняет код из-за постоянных проверок типа (type assertion
).