如何干净地处理结构状态错误?

时间:2019-03-02 06:41:57

标签: go

因此,我刚开始接触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

哪种方法有效,但我不喜欢每张支票有一定的嵌套量。

  1. 更改错误返回以指示它已在本地更新,但未保存
  2. 按照上述说明进行操作
  3. 将保存逻辑分解为一个func Update() : err函数,以最大程度地减少所需的检查次数(仅想到这一点-认为我喜欢这一点)

我觉得这些都不是完美的。我缺少明显的东西吗?

2 个答案:

答案 0 :(得分:2)

更新

您的坚持方式会导致许多问题:

  • 您可以白炽化原始对象
  • 您在一种方法中混合了不同的层,这使代码非常脆弱
  • 方法,做得太多

我建议将更新和持久性分开。 Playground

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
}

答案 1 :(得分:1)

Alexey是正确的,但是如果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
    })
}