Основные проблемы: глобальные переменные (items, removalChannel, order) — не потокобезопасны, init() создаёт слайс длины 100, но append работает некорректно (нужно nil или make с 0). RemoveItemFromShoppingCart использует горутину и канал, что избыточно и может привести к гонке данных. CalculateVAT использует vatRate как глобальную, лучше передавать параметром. ListAndCalculateVAT выводит в stdout, что негибко. CreateOrder отправляет email синхронно, лучше асинхронно. SendOrderConfirmationEmail хранит пароль в коде. Предлагается: инкапсулировать в структуру с мьютексом, убрать init, исправить инициализацию слайса, убрать горутину из RemoveItem, использовать контекст, вынести отправку email в отдельный сервис.
Code Review: Корзина в Go
Основные проблемы
1. Глобальные переменные и потокобезопасность
var items []Product
var removalChannel chan bool
var order Order
- Глобальные переменные не потокобезопасны
- При конкурентном доступе из нескольких горутин возможны гонки данных
- Отсутствует синхронизация при чтении/записи
2. Некорректная инициализация слайса
func init() {
items = make([]Product, 100)
}
- Создаётся слайс с 100 нулевыми элементами
appendбудет добавлять элементы после этих 100 пустых позиций- Правильно:
items = make([]Product, 0)илиvar items []Product
3. Избыточное использование горутины и канала
func RemoveItemFromShoppingCart(nameOfItem string) {
go func() {
// ...
removalChannel <- true
}()
<-removalChannel
}
- Горутина запускается и сразу блокируется на чтении из канала
- Нет реальной асинхронности — код выполняется последовательно
- Возможна гонка данных при модификации слайса
4. Проблемы с отправкой email
- Пароль хранится в коде (security issue)
- Используется
smtp.PlainAuth— пароль передаётся в открытом виде - Синхронная отправка блокирует выполнение
- Жёстко закодированы SMTP-параметры
5. Смешение ответственности
ListAndCalculateVATвыводит в stdout и считает налогCreateOrderзанимается и расчётами, и отправкой email- Нарушение Single Responsibility Principle
Улучшенная реализация
package cart
import (
"context"
"fmt"
"sync"
)
// Product представляет товар в корзине
type Product struct {
Name string
Price float64
}
// Cart представляет потокобезопасную корзину покупателя
type Cart struct {
mu sync.RWMutex
items []Product
vatRate float64
}
// NewCart создаёт новую корзину
func NewCart(vatRate float64) *Cart {
return &Cart{
items: make([]Product, 0),
vatRate: vatRate,
}
}
// AddItem добавляет товар в корзину
func (c *Cart) AddItem(item Product) {
c.mu.Lock()
defer c.mu.Unlock()
c.items = append(c.items, item)
}
// RemoveItem удаляет товар по имени
func (c *Cart) RemoveItem(name string) error {
c.mu.Lock()
defer c.mu.Unlock()
for i, item := range c.items {
if item.Name == name {
c.items = append(c.items[:i], c.items[i+1:]...)
return nil
}
}
return fmt.Errorf("item %q not found", name)
}
// CalculateVAT вычисляет сумму НДС
func (c *Cart) CalculateVAT() float64 {
c.mu.RLock()
defer c.mu.RUnlock()
subtotal := 0.0
for _, item := range c.items {
subtotal += item.Price
}
return subtotal * (c.vatRate / 100)
}
// GetItems возвращает копию списка товаров
func (c *Cart) GetItems() []Product {
c.mu.RLock()
defer c.mu.RUnlock()
items := make([]Product, len(c.items))
copy(items, c.items)
return items
}
// Order представляет заказ
type Order struct {
Items []Product
TotalPrice float64
VATAmount float64
}
// CreateOrder создаёт заказ и возвращает его
func (c *Cart) CreateOrder() Order {
c.mu.RLock()
defer c.mu.RUnlock()
items := make([]Product, len(c.items))
copy(items, c.items)
subtotal := 0.0
for _, item := range items {
subtotal += item.Price
}
vatAmount := subtotal * (c.vatRate / 100)
totalPrice := subtotal + vatAmount
return Order{
Items: items,
TotalPrice: totalPrice,
VATAmount: vatAmount,
}
}
// EmailService отвечает за отправку email
type EmailService struct {
from string
host string
port string
// В реальном проекте использовать безопасное хранилище
// или переменные окружения
}
// NewEmailService создаёт новый сервис отправки email
func NewEmailService(from, host, port string) *EmailService {
return &EmailService{
from: from,
host: host,
port: port,
}
}
// SendOrderConfirmation отправляет подтверждение заказа
func (s *EmailService) SendOrderConfirmation(ctx context.Context, to string, order Order) error {
// Формируем письмо
body := fmt.Sprintf("Thank you for your order!\n\n")
body += "Here are the items in your order:\n"
for _, item := range order.Items {
body += fmt.Sprintf("- %s: $%.2f\n", item.Name, item.Price)
}
body += fmt.Sprintf("\nSubtotal: $%.2f\n", order.TotalPrice - order.VATAmount)
body += fmt.Sprintf("VAT: $%.2f\n", order.VATAmount)
body += fmt.Sprintf("Total: $%.2f\n", order.TotalPrice)
// Асинхронная отправка
go func() {
select {
case <-ctx.Done():
return
default:
// Здесь реальная отправка email
// Использовать безопасную аутентификацию
fmt.Printf("Sending email to %s: %s\n", to, body)
}
}()
return nil
}
// Пример использования
func Example() {
cart := NewCart(10.0)
cart.AddItem(Product{Name: "Laptop", Price: 1000.0})
cart.AddItem(Product{Name: "Mouse", Price: 50.0})
order := cart.CreateOrder()
emailService := NewEmailService(
"shop@example.com",
"smtp.example.com",
"587",
)
ctx := context.Background()
if err := emailService.SendOrderConfirmation(ctx, "customer@example.com", order); err != nil {
fmt.Printf("Failed to send email: %v\n", err)
}
}
Ключевые улучшения
- Инкапсуляция: Вся логика внутри структуры
Cart - Потокобезопасность:
sync.RWMutexдля конкурентного доступа - Разделение ответственности:
Cartуправляет товарами,EmailService— отправкой - Контекст: Поддержка отмены операций через
context.Context - Безопасность: Пароли не хранятся в коде
- Гибкость: Возможность подмены реализации (интерфейсы)
- Тестируемость: Чистые функции, легко мокировать зависимости
Дополнительные рекомендации
- Добавить интерфейсы для тестирования
- Использовать
go-sqlite3илиredisдля персистентности - Добавить метрики и логирование
- Реализовать graceful shutdown
- Использовать DI-контейнер для управления зависимостями
Задача