Как правильно обрабатывать ошибки с состоянием структуры? - PullRequest
0 голосов
/ 02 марта 2019

Так что я просто промокаю ногами от Go и пытаюсь аккуратно обрабатывать ошибки в Go-way.Одним из результатов этого является наличие type Movie struct с методами для обновления записи и синхронизации с хранилищем данных.Итак, пример метода:

func (movie Movie) SetTitle(title string) : error {
    prevTitle := movie.Title
    movie.Title = title
    json, err := json.Marshal(movie)
    if (err != nil) {
        movie.Title = prevTitle
        return err
    }

    err = db.SetValue(movie.id, json)
    if (err != nil) {
        movie.Title = prevTitle
        return err
    }

    return nil
}

Проблема, описанная выше, заключается в том, что «очистка» для сбойной операции заключается в сохранении предыдущего состояния и восстановлении в случае необходимости.Но это кажется очень неосуществимым - так легко сделать новую проверку ошибок в будущем, только чтобы вернуться без надлежащей очистки.Это было бы еще хуже с несколькими такими «чистками».

Для сравнения, в других языках я бы обернул весь бит в try / catch и выполнял очистку только внутри одного блока catch.Здесь я могу:

  1. Создать копию movie, выполнить всю работу с копией, а затем копировать обратно в исходный объект только после того, как все будет успешно
  2. Изменитьлогика:
if json, err := json.Marshal(movie); err == nil {
  if err = db.SetValue(...); err == nil {
    return nil
  }
}

movie.Title = prevTitle;
return err

Что работает, но я не люблю иметь уровень вложенности на проверку.

Измените возвращаемую ошибку, чтобы указать, что она была обновлена ​​локально, но не сохранена Сделайте это, как описано выше Разбейте логику сохранения на функцию func Update() : err, чтобы минимизировать количествонужны проверки (просто подумал об этом - думаю, мне нравится этот)

Я чувствую, что ничего из этого не идеально;я что-то упускаю очевидное?

Ответы [ 2 ]

0 голосов
/ 02 марта 2019

Алексей верен, но если структура Movie имеет поля указателя или среза, они не будут скопированы.

Вот пример, который вручную копирует поле среза (Tags) перед каждым обновлением, а также имеет хороший метод транзакции (update), я думаю, вы могли бы использовать:

type Movie struct {
    id int
    Title string
    Year int
    Tags []string
}

func (m *Movie) update(fn func(m *Movie) error) error {
    // Make a copy of Movie.
    movieCopy := *m

    // Manually copy slice and pointer fields.
    movieCopy.Tags = make([]string, 0, len(m.Tags))
    copy(movieCopy.Tags, m.Tags)

    // Run the update transaction on the copy.
    if err := fn(&movieCopy); err != nil {
        return err
    }

    // Save to db.
    data, err := json.Marshal(movieCopy)
    if err != nil {
        return err
    }
    return db.SetValue(m.id, data)
}

func (m *Movie) SetTitle(title string) error {
    m.update(func(mm *Movie) error {
        mm.Title = title
        return nil
    })
}

func (m *Movie) SetYear(year int) error {
    m.update(func(mm *Movie) error {
        mm.Year = year
        return nil
    })
}
0 голосов
/ 02 марта 2019

Обновление

То, как вы сохраняете, может вызвать множество проблем:

  • вы можете мутировать исходный объект накаливанием
  • вы смешиваете разные слои в одном методе, этоделает код очень хрупким
  • метод, делает слишком много

Я предлагаю, отдельное обновление и сохранение. Детская площадка

type Movie struct {
    id    int
    Title string
}

func (m *Movie) Persist() error {
    json, err := json.Marshal(m)
    if err != nil {
        return err
    }

    log.Printf("Storing in db: %s", json)

    return nil
}

func main() {
    m := &Movie{1, "Matrix"}
    m.Title = "Iron Man"

    if err := m.Persist(); err != nil {
        log.Fatalln(err)
    }

}

Старый ответ

В вашем примере вы используете приемник по значению.В этом случае вы получаете копию структуры в методе, вы можете свободно изменять эту копию, но все изменения не будут видны снаружи.

func (movie Movie) SetTitleValueSemantic(title string) error {
    movie.Title = title
    json, err := json.Marshal(movie)
    if err != nil {
        return err
    }

    log.Printf("Serialized: %s", json)

    return nil
}

playgorund: https://play.golang.org/p/mVnQ66TCaG9

Я бы настоятельно рекомендовал избегать такого стиля кодирования.Если вам ДЕЙСТВИТЕЛЬНО нужно что-то подобное, вот пример для вдохновения:

детская площадка: https://play.golang.org/p/rHacnsRLkEE

func (movie *Movie) SetTitle(title string) (result *Movie, e error) {
    movieCopy := new(Movie)
    *movieCopy = *movie
    result = movie

    defer func() {
        if e != nil {
            result = movieCopy
        }
    }()

    movie.Title = title
    serialized, e := json.Marshal(movie)
    if e != nil {
        return
    }

    log.Printf("Serialized: %s", serialized)

    return
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...