Задачи на рефакторинг
1) Какие проблемы есть в этом коде и как можно его улучшить?, как его улучшить не меняя сам код (при помощи декораторов)?
type FileLogger struct{ file *os.File }
func NewFileLogger(fileName string) (*FileLogger, error) {
f, err := os.Create(fileName)
if err != nil {
return nil, err
}
return &FileLogger{f}, nil
}
func (f FileLogger) Log(message string) error {
_, err := f.file.WriteString(message + "\n")
return err
}
func (f FileLogger) Close() error {
return f.file.Close()
}
Этот код на Go в целом выполняет свою основную задачу - логирование сообщений в файл. Однако, есть несколько моментов, которые можно улучшить:
Обработка ошибок
Во-первых, стоит обратить внимание на обработку ошибок. В функции NewFileLogger
ошибка корректно возвращается, но в функции Log
ошибка просто возвращается, не обрабатывается. Это может привести к проблемам, если вызывающий код не будет проверять ошибку. Лучше всего обрабатывать ошибки в самом коде логгера, чтобы гарантировать, что они не будут проигнорированы.
Использование defer
Во-вторых, в функции NewFileLogger
стоит использовать defer
для закрытия файла, чтобы гарантировать, что он будет закрыт даже в случае ошибки. Это поможет избежать утечек ресурсов.
Использование io.Writer
В-третьих, вместо использования f.file.WriteString
можно использовать io.Writer
интерфейс, который позволит более гибко работать с выводом. Это также упростит тестирование, так как можно будет подставлять mock-объекты, реализующие io.Writer
.
Использование sync.Mutex
Наконец, если предполагается, что FileLogger
будет использоваться в многопоточном окружении, стоит добавить sync.Mutex
для защиты доступа к файлу от одновременного использования.
Вот как может выглядеть улучшенная версия кода:
type FileLogger struct {
file *os.File
mu sync.Mutex
}
func NewFileLogger(fileName string) (*FileLogger, error) {
f, err := os.Create(fileName)
if err != nil {
return nil, err
}
return &FileLogger{file: f}, nil
}
func (f *FileLogger) Log(message string) error {
f.mu.Lock()
defer f.mu.Unlock()
_, err := fmt.Fprintln(f.file, message)
return err
}
func (f *FileLogger) Close() error {
return f.file.Close()
}
В этой версии:
Ошибки обрабатываются в самом коде логгера.
Используется
defer
для закрытия файла.Используется
io.Writer
интерфейс для вывода сообщений.Добавлен
sync.Mutex
для защиты доступа к файлу в многопоточном окружении.
Эти изменения делают код более надежным, безопасным и гибким.
1.1 Если нам нельзя менять сам код, то вот несколько потенциальных проблем, которые можно решить с помощью декораторов:
Проблемы в коде
Отсутствие проверки ошибок: В методе
Log()
не проверяется ошибка, возвращаемая функциейWriteString()
. Это может привести к потере данных, если запись в файл не удалась.Отсутствие обработки ошибок при закрытии файла: В методе
Close()
не обрабатывается ошибка, возвращаемая функциейClose()
. Это может привести к утечке ресурсов, если закрытие файла не удалось.Отсутствие гибкости: Текущая реализация
FileLogger
жестко привязана к файловой системе. Это может затруднить использование других типов хранилищ, таких как облачные сервисы или базы данных.
Улучшение кода с помощью декораторов
Для решения этих проблем можно использовать декораторы. Декораторы позволяют добавлять дополнительную функциональность к существующим структурам и функциям, не изменяя их исходный код.
Декоратор для обработки ошибок
Создадим декоратор, который будет обрабатывать ошибки, возникающие при записи в файл и его закрытии:
type ErrorHandlingLogger struct {
logger *FileLogger
}
func NewErrorHandlingLogger(fileName string) (*ErrorHandlingLogger, error) {
logger, err := NewFileLogger(fileName)
if err != nil {
return nil, err
}
return &ErrorHandlingLogger{logger}, nil
}
func (l *ErrorHandlingLogger) Log(message string) error {
err := l.logger.Log(message)
if err != nil {
// Обработка ошибки, например, вывод в консоль или запись в другой журнал
fmt.Printf("Ошибка записи в файл: %v\n", err)
}
return err
}
func (l *ErrorHandlingLogger) Close() error {
err := l.logger.Close()
if err != nil {
// Обработка ошибки, например, вывод в консоль или запись в другой журнал
fmt.Printf("Ошибка закрытия файла: %v\n", err)
}
return err
}
Теперь, вместо использования FileLogger
напрямую, можно использовать ErrorHandlingLogger
, который будет обрабатывать ошибки, возникающие при записи в файл и его закрытии.
Декоратор для гибкости
Для повышения гибкости можно создать декоратор, который позволит использовать различные типы хранилищ для журналирования:
type StorageLogger struct {
storage Storage
}
func NewStorageLogger(storage Storage) *StorageLogger {
return &StorageLogger{storage}
}
func (l *StorageLogger) Log(message string) error {
return l.storage.Write(message)
}
func (l *StorageLogger) Close() error {
return l.storage.Close()
}
// Интерфейс Storage определяет методы для работы с хранилищем
type Storage interface {
Write(data string) error
Close() error
}
// Реализация Storage для файловой системы
type FileStorage struct {
file *os.File
}
func NewFileStorage(fileName string) (*FileStorage, error) {
f, err := os.Create(fileName)
if err != nil {
return nil, err
}
return &FileStorage{f}, nil
}
func (s *FileStorage) Write(data string) error {
_, err := s.file.WriteString(data + "\n")
return err
}
func (s *FileStorage) Close() error {
return s.file.Close()
}
// Реализация Storage для облачного хранилища
type CloudStorage struct {
// Реализация для облачного хранилища
}
func NewCloudStorage(config Config) (*CloudStorage, error) {
// Создание подключения к облачному хранилищу
return &CloudStorage{}, nil
}
func (s *CloudStorage) Write(data string) error {
// Запись данных в облачное хранилище
return nil
}
func (s *CloudStorage) Close() error {
// Закрытие подключения к облачному хранилищу
return nil
}
Теперь, вместо использования FileLogger
напрямую, можно использовать StorageLogger
с различными реализациями Storage
, таким образом обеспечивая гибкость и возможность использования разных типов хранилищ.
Пример использования:
// Использование FileStorage
fileStorage, _ := NewFileStorage("log.txt")
fileLogger := NewStorageLogger(fileStorage)
fileLogger.Log("Сообщение 1")
fileLogger.Close()
// Использование CloudStorage
cloudStorage, _ := NewCloudStorage(config)
cloudLogger := NewStorageLogger(cloudStorage)
cloudLogger.Log("Сообщение 2")
cloudLogger.Close()
Таким образом, использование декораторов позволяет улучшить код, добавляя дополнительную функциональность, не изменяя исходный код.
2) Необходимо сделать ревью следующего кода:
Вводные: есть условный репозиторий, стандартные CRUD методы.
Есть некая доменная модель и модель для БД и некая ORM для построения запросов.
Цель - подсветить критичные места, ошибки или указать как сделать лучше.
func (s *Storage) CreateProduct(ctx context.Context, wp *model.Product) error {
dbM := ProductModelToDB(ctx, wp)
_, err := s.db.NewInsert().Model(&dbM).Exec(ctx)
if err != nil {
return errors.Wrap(err, "CreateProduct")
}
return nil
}
func (s *Storage) ReadProduct(ctx context.Context, wp *model.Product) (*model.Product, error) {
dbIn := ProductModelToDB(ctx, wp)
dbM := Product{}
err := s.db.NewSelect().
Model(&dbM).
Where("wp.id = ?", dbIn.ID).
Scan(ctx)
if err != nil {
return nil, errors.Wrap(err, "ReadProduct")
}
dbOut := dbIn.ToModel(ctx)
return &dbOut, nil
}
func (s *Storage) UpdateProduct(ctx context.Context, wp *model.Product) error {
dbIn := ProductModelToDB(ctx, wp)
values := s.db.NewValues(&dbIn)
_, err := s.db.NewUpdate().
With("_data", values).
Model(&Product{}).
TableExpr("_data").
Set("guid = _data.guid").
Where("wp.guid = _data.id").
Exec(ctx)
if err != nil {
return errors.Wrap(err, "UpdateProduct")
}
return nil
}
func (s *Storage) DeleteProduct(ctx context.Context, wp *model.Product) error {
dbM := ProductModelToDB(ctx, wp)
_, err := s.db.NewDelete().
Model(&Product{}).
Where("wp.id", dbM.ID).
Exec(ctx)
if err != nil {
return errors.Wrap(err, "DeleteProduct")
}
return nil
}
Рассмотрим, какие проблемы есть в коде:
func (s *Storage) CreateProduct(ctx context.Context, wp *model.Product) error {
dbM := ProductModelToDB(ctx, wp)
_, err := s.db.NewInsert().Model(&dbM).Exec(ctx)
if err != nil {
return errors.Wrap(err, "CreateProduct")
}
return nil
}
func (s *Storage) ReadProduct(ctx context.Context, wp *model.Product) (*model.Product, error) {
dbIn := ProductModelToDB(ctx, wp)
dbM := Product{}
err := s.db.NewSelect().
Model(&dbM).
Where("wp.id = ?", dbIn.ID). // Ошибка: лучше использовать запросы в явном виде, например, "id = ?"
Scan(ctx)
if err != nil {
return nil, errors.Wrap(err, "ReadProduct")
}
dbOut := dbIn.ToModel(ctx) // Ошибка: нужно использовать dbM, так как это результат запроса
return &dbOut, nil
}
func (s *Storage) UpdateProduct(ctx context.Context, wp *model.Product) error {
dbIn := ProductModelToDB(ctx, wp)
values := s.db.NewValues(&dbIn)
_, err := s.db.NewUpdate().
With("_data", values).
Model(&Product{}).
TableExpr("_data").
Set("guid = _data.guid").
Where("wp.guid = _data.id"). // Ошибка: лучше использовать запросы в явном виде, например, "guid = _data.id"
Exec(ctx)
if err != nil {
return errors.Wrap(err, "UpdateProduct")
}
return nil
}
func (s *Storage) DeleteProduct(ctx context.Context, wp *model.Product) error {
dbM := ProductModelToDB(ctx, wp)
_, err := s.db.NewDelete().
Model(&Product{}).
Where("wp.id", dbM.ID). // Ошибка: лучше использовать запросы в явном виде, например, "id = ?"
Exec(ctx)
if err != nil {
return errors.Wrap(err, "DeleteProduct")
}
return nil
}
Исправленный код с учетом всех замечаний:
func (s *Storage) CreateProduct(ctx context.Context, wp *model.Product) error {
dbM := ProductModelToDB(ctx, wp)
_, err := s.db.NewInsert().Model(&dbM).Exec(ctx)
if err != nil {
return errors.Wrap(err, "CreateProduct")
}
return nil
}
func (s *Storage) ReadProduct(ctx context.Context, wp *model.Product) (*model.Product, error) {
dbIn := ProductModelToDB(ctx, wp)
dbM := Product{}
err := s.db.NewSelect().
Model(&dbM).
// Используем явный запрос для фильтрации по ID
Where("id = ?", dbIn.ID).
Scan(ctx)
if err != nil {
return nil, errors.Wrap(err, "ReadProduct")
}
// Используем dbM, так как это результат запроса
dbOut := dbM.ToModel(ctx)
return &dbOut, nil
}
func (s *Storage) UpdateProduct(ctx context.Context, wp *model.Product) error {
dbIn := ProductModelToDB(ctx, wp)
values := s.db.NewValues(&dbIn)
_, err := s.db.NewUpdate().
With("_data", values).
Model(&Product{}).
TableExpr("_data").
Set("guid = _data.guid").
// Используем явный запрос для фильтрации по GUID
Where("guid = _data.id").
Exec(ctx)
if err != nil {
return errors.Wrap(err, "UpdateProduct")
}
return nil
}
func (s *Storage) DeleteProduct(ctx context.Context, wp *model.Product) error {
dbM := ProductModelToDB(ctx, wp)
_, err := s.db.NewDelete().
Model(&Product{}).
// Используем явный запрос для фильтрации по ID
Where("id = ?", dbM.ID).
Exec(ctx)
if err != nil {
return errors.Wrap(err, "DeleteProduct")
}
return nil
}
Пояснения к исправлениям:
ReadProduct:
Исправлено:
Where("wp.id = ?", dbIn.ID)
заменено наWhere("id = ?", dbIn.ID)
для явного указания поляid
.Исправлено:
dbOut := dbIn.ToModel(ctx)
заменено наdbOut := dbM.ToModel(ctx)
, так какdbM
содержит результат запроса.
UpdateProduct:
Исправлено:
Where("wp.guid = _data.id")
заменено наWhere("guid = _data.id")
для явного указания поляguid
.
DeleteProduct:
Исправлено:
Where("wp.id", dbM.ID)
заменено наWhere("id = ?", dbM.ID)
для явного указания поляid
.
3) Необходимо сделать рефакторинг следующего кода, исправить ошибки, проблемы:
type OrderService struct {
BookingService BookingService
UserService UserService
}
type UserService interface {
LockUser(User) error
UnlockUser(User) error
}
type User struct {
ID string
}
type Receipt struct {
ID string
BookingCode string
BookedAt string
}
type BookingService interface {
BookFlight() (string, *BookingServiceError)
}
type BookingServiceError struct {
error
TryAgain bool
}
func (s *OrderService) HandleBookingOrder(user User) *Receipt {
receipt := Receipt{ID: uuid.New().String()}
if err := s.UserService.LockUser(user); err != nil {
log.Logger.Err(err)
return nil
}
for {
bookingCode, err := s.BookingService.BookFlight()
switch {
case err == nil:
receipt.BookedAt = time.Now().Format(time.RFC3339)
receipt .BookingCode = bookingCode
return &receipt
case err. TryAgain:
default:
log.Logger.Err(err)
break
}
}
if err := s.UserService.UnlockUser(user); err != nil {
log.Logger.Err(err)
return nil
}
return &receipt
}
В коде есть несколько ошибок и недочетов, которые нужно исправить. Давайте рассмотрим их по порядку:
Импорт необходимых пакетов:
В коде используются функции и типы, которые требуют импортирования соответствующих пакетов, таких как
uuid
,log
, иtime
.
Проблемы с
BookingServiceError
:В структуре
BookingServiceError
полеerror
определено неправильно. Оно должно быть либо встроенным типом, либо полем с именем.
Проблемы с циклом и
break
:Внутри цикла
for
используетсяbreak
, который выходит из цикла, но не завершает метод. Это приведет к тому, что метод продолжит выполнение и может вернуть некорректное значение.
Проблемы с
UnlockUser
:Метод
UnlockUser
вызывается после цикла, но если цикл завершится с ошибкой,UnlockUser
не будет вызван.
Проблемы с логированием:
В коде используется
log.Logger.Err
, но это не стандартный метод логирования. Возможно, вы имели в видуlog.Println
или другой метод из пакетаlog
.
Проблемы с
uuid.New().String()
:Для генерации UUID нужно импортировать соответствующий пакет и использовать его правильно.
Исправленный код
package main
import (
"fmt"
"log"
"time"
"github.com/google/uuid"
)
type OrderService struct {
BookingService BookingService
UserService UserService
}
type UserService interface {
LockUser(User) error
UnlockUser(User) error
}
type User struct {
ID string
}
type Receipt struct {
ID string
BookingCode string
BookedAt string
}
type BookingService interface {
BookFlight() (string, *BookingServiceError)
}
type BookingServiceError struct {
Err error
TryAgain bool
}
func (e *BookingServiceError) Error() string {
return e.Err.Error()
}
func (s *OrderService) HandleBookingOrder(user User) *Receipt {
receipt := Receipt{ID: uuid.New().String()}
if err := s.UserService.LockUser(user); err != nil {
log.Println("Error locking user:", err)
return nil
}
defer func() {
if err := s.UserService.UnlockUser(user); err != nil {
log.Println("Error unlocking user:", err)
}
}()
for {
bookingCode, err := s.BookingService.BookFlight()
switch {
case err == nil:
receipt.BookedAt = time.Now().Format(time.RFC3339)
receipt.BookingCode = bookingCode
return &receipt
case err.TryAgain:
// Retry booking
continue
default:
log.Println("Error booking flight:", err)
return nil
}
}
}
func main() {
// Пример использования OrderService
fmt.Println("Пример использования OrderService")
}
Объяснение исправлений
Импорт пакетов:
Импортированы необходимые пакеты:
fmt
,log
,time
, иgithub.com/google/uuid
.
Исправление
BookingServiceError
:Поле
error
заменено наErr
, и добавлен методError()
для реализации интерфейсаerror
.
Использование
defer
дляUnlockUser
:Использован
defer
для вызоваUnlockUser
, чтобы гарантировать, что пользователь будет разблокирован в любом случае.
Исправление логирования:
Использован стандартный метод
log.Println
для логирования ошибок.
Исправление цикла:
В случае ошибки, которая не требует повторной попытки, метод возвращает
nil
, завершая выполнение.
Кроме того:
Для того чтобы функция HandleBookingOrder
могла работать в высоконагруженной системе, необходимо учесть несколько аспектов, таких как управление конкурентностью, обработка ошибок, логирование и оптимизация производительности. Вот несколько предложений по улучшению:
1. Управление конкурентностью
Использование горутин и каналов для обработки заказов может улучшить производительность и масштабируемость.
2. Ограничение числа повторных попыток
Необходимо ограничить количество повторных попыток бронирования, чтобы избежать бесконечных циклов и перегрузки системы.
3. Тайм-ауты
Добавление тайм-аутов для операций блокировки пользователя и бронирования рейсов поможет избежать зависания системы.
4. Логирование
Использование структурированного логирования для улучшения анализа и мониторинга.
5. Пул горутин
Использование пула горутин для управления количеством одновременно выполняемых задач.
Улучшенный код
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/google/uuid"
)
// OrderService представляет сервис для обработки заказов
type OrderService struct {
BookingService BookingService
UserService UserService
}
// UserService определяет интерфейс для управления пользователями
type UserService interface {
LockUser(ctx context.Context, user User) error
UnlockUser(ctx context.Context, user User) error
}
// User представляет пользователя
type User struct {
ID string
}
// Receipt представляет квитанцию о бронировании
type Receipt struct {
ID string
BookingCode string
BookedAt string
}
// BookingService определяет интерфейс для бронирования
type BookingService interface {
BookFlight(ctx context.Context) (string, *BookingServiceError)
}
// BookingServiceError представляет ошибку сервиса бронирования
type BookingServiceError struct {
Err error
TryAgain bool
}
// Error реализует интерфейс error для BookingServiceError
func (e *BookingServiceError) Error() string {
return e.Err.Error()
}
// HandleBookingOrder обрабатывает заказ на бронирование
func (s *OrderService) HandleBookingOrder(ctx context.Context, user User) *Receipt {
receipt := Receipt{ID: uuid.New().String()}
// Устанавливаем тайм-аут для блокировки пользователя
lockCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := s.UserService.LockUser(lockCtx, user); err != nil {
log.Printf("Error locking user %s: %v", user.ID, err)
return nil
}
defer func() {
// Устанавливаем тайм-аут для разблокировки пользователя
unlockCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := s.UserService.UnlockUser(unlockCtx, user); err != nil {
log.Printf("Error unlocking user %s: %v", user.ID, err)
}
}()
// Ограничиваем количество повторных попыток
const maxRetries = 3
for retries := 0; retries < maxRetries; retries++ {
// Устанавливаем тайм-аут для бронирования рейса
bookingCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
bookingCode, err := s.BookingService.BookFlight(bookingCtx)
if err == nil {
receipt.BookedAt = time.Now().Format(time.RFC3339)
receipt.BookingCode = bookingCode
return &receipt
}
if err.TryAgain {
log.Printf("Temporary error booking flight: %v. Retrying...", err)
time.Sleep(2 * time.Second) // Добавляем задержку перед повторной попыткой
continue
}
log.Printf("Error booking flight: %v", err)
return nil
}
log.Printf("Failed to book flight after %d attempts", maxRetries)
return nil
}
func main() {
// Пример использования OrderService
fmt.Println("Пример использования OrderService")
}
Объяснение улучшений
Контекст и тайм-ауты:
Использование контекста (
context.Context
) для управления временем выполнения операций блокировки пользователя и бронирования рейсов.Установка тайм-аутов для предотвращения зависания операций.
Ограничение числа повторных попыток:
Добавлено ограничение на количество повторных попыток бронирования (
maxRetries
), чтобы избежать бесконечных циклов.
Задержка перед повторной попыткой:
Добавлена задержка перед повторной попыткой бронирования, чтобы избежать перегрузки системы.
Структурированное логирование:
Использование
log.Printf
для более информативного логирования, включая идентификатор пользователя и описание ошибки.
Пул горутин:
В данном примере пул горутин не реализован, но это можно сделать с помощью библиотеки, такой как
golang.org/x/sync/errgroup
, для управления количеством одновременно выполняемых задач.
Заключение
Эти улучшения делают функцию более устойчивой к высоким нагрузкам, улучшая управление конкурентностью, обработку ошибок и логирование. Использование контекста и тайм-аутов помогает избежать зависания операций, а ограничение числа повторных попыток и задержка перед повторной попыткой помогают избежать перегрузки системы.
4) Что делает данный код и как его улучшить?
var c int
type Trace struct {
// Some fields here
}
type Sender interface {|
Send(Trace)
}
func Do(sender Sender, tr Trace) {
c++
if c == 100 {
sender.Send(tr)
c=0
}
}
Этот код определяет счетчик и интерфейс для отправки данных, а также функцию, которая использует этот счетчик для управления отправкой данных. Давайте разберем его по частям:
Переменная c
c
var c int
Это глобальная переменная
c
типаint
, которая используется как счетчик.
Структура Trace
Trace
type Trace struct {
// Some fields here
}
Это структура
Trace
, которая может содержать какие-то поля (они не указаны в данном коде).
Интерфейс Sender
Sender
type Sender interface {
Send(Trace)
}
Это интерфейс
Sender
, который определяет методSend
, принимающий объект типаTrace
.
Функция Do
Do
func Do(sender Sender, tr Trace) {
c++
if c == 100 {
sender.Send(tr)
c = 0
}
}
Функция
Do
принимает два аргумента:sender
типаSender
(интерфейс, который должен реализовывать методSend
).tr
типаTrace
(структура, которую нужно отправить).
Внутри функции:
Сначала увеличивается значение глобальной переменной
c
на 1 (c++
).Затем проверяется, равно ли значение
c
100.Если
c
равно 100, вызывается методSend
интерфейсаSender
с аргументомtr
.После этого значение
c
сбрасывается в 0 (c = 0
).
Объяснение работы
Счетчик: Глобальная переменная
c
используется как счетчик вызовов функцииDo
.Отправка данных: Метод
Send
интерфейсаSender
вызывается только тогда, когда счетчик достигает значения 100.Сброс счетчика: После вызова метода
Send
, счетчик сбрасывается в 0.
Пример использования
Предположим, у нас есть структура, которая реализует интерфейс Sender
:
type MySender struct{}
func (ms MySender) Send(tr Trace) {
fmt.Println("Sending trace:", tr)
}
func main() {
sender := MySender{}
trace := Trace{}
for i := 0; i < 200; i++ {
Do(sender, trace)
}
}
В этом примере функция
Do
будет вызвана 200 раз.Метод
Send
будет вызван дважды: на 100-м и 200-м вызовах функцииDo
.
То есть код реализует простой механизм счетчика для управления частотой вызова метода Send
интерфейса Sender
. Метод Send
вызывается только тогда, когда счетчик достигает значения 100, после чего счетчик сбрасывается. Это может быть полезно для ограничения частоты отправки данных или выполнения каких-либо действий.
Данный код можно улучшить с точки зрения управления состоянием, конкурентности и читаемости. Вот несколько предложений:
1. Избегайте глобальных переменных
Глобальные переменные могут привести к проблемам с конкурентностью и сложностями в тестировании. Лучше использовать структуру для хранения состояния.
2. Управление конкурентностью
Если функция Do
будет вызываться из нескольких горутин, то использование глобальной переменной c
может привести к гонкам данных. Для решения этой проблемы можно использовать мьютексы или атомарные операции.
3. Улучшение читаемости
Добавление комментариев и более явных имен переменных может улучшить читаемость кода.
Улучшенный код
package main
import (
"fmt"
"sync"
"sync/atomic"
)
// Trace представляет структуру с какими-то полями
type Trace struct {
// Some fields here
}
// Sender определяет интерфейс для отправки данных
type Sender interface {
Send(Trace)
}
// CounterSender управляет счетчиком и отправкой данных
type CounterSender struct {
sender Sender
count int32
}
// NewCounterSender создает новый CounterSender
func NewCounterSender(sender Sender) *CounterSender {
return &CounterSender{
sender: sender,
count: 0,
}
}
// Do увеличивает счетчик и отправляет данные, если счетчик достигает 100
func (cs *CounterSender) Do(tr Trace) {
// Увеличиваем счетчик атомарно
newCount := atomic.AddInt32(&cs.count, 1)
if newCount == 100 {
cs.sender.Send(tr)
// Сбрасываем счетчик атомарно
atomic.StoreInt32(&cs.count, 0)
}
}
// Пример реализации интерфейса Sender
type MySender struct{}
func (ms MySender) Send(tr Trace) {
fmt.Println("Sending trace:", tr)
}
func main() {
sender := MySender{}
counterSender := NewCounterSender(sender)
trace := Trace{}
var wg sync.WaitGroup
for i := 0; i < 200; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counterSender.Do(trace)
}()
}
wg.Wait()
}
Объяснение улучшений
Избегание глобальных переменных:
Вместо глобальной переменной
c
используется структураCounterSender
, которая хранит состояние счетчика.
Управление конкурентностью:
Используются атомарные операции (
atomic.AddInt32
иatomic.StoreInt32
) для безопасного увеличения и сброса счетчика в многопоточной среде.
Улучшение читаемости:
Добавлены комментарии и более явные имена переменных и методов.
Создана функция
NewCounterSender
для инициализации структурыCounterSender
.
Пример использования:
В функции
main
создается несколько горутин для демонстрации конкурентного вызова методаDo
.
Заключение
Эти улучшения делают код более безопасным для использования в многопоточной среде, улучшают его читаемость и тестируемость. Использование атомарных операций и структуры для хранения состояния помогает избежать проблем с гонками данных и делает код более модульным и понятным.
Last updated