doufei3561 2019-03-02 06:41
浏览 9
已采纳

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

So I'm just getting my feet wet with Go, and I'm trying to cleanly handle errors in the Go-way. One result is that having a type Movie struct with methods to update the record, and sync to a data store. So an example method:

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
}

The issue above is the 'cleanup' for a failed operation is to save the previous state, and restore as appropriate. But this feels very unmaintainable - it's so easy for a new error check to be made in the future, only to return without the proper cleanup. This would get even worse with multiple such 'cleanups'.

So to contrast, in other languages, I would wrap the whole bit in a try/catch, and handle cleanup just inside a single catch block. Here, I can:

  1. Create a copy of movie, do all the stuff on the copy, and then only copy back to the original object once everything is successful
  2. Change the logic to:
if json, err := json.Marshal(movie); err == nil {
  if err = db.SetValue(...); err == nil {
    return nil
  }
}

movie.Title = prevTitle;
return err

Which works, but I don't love having a level of nesting per check.

  1. Change the error return to indicate it was updated locally, but not saved
  2. Do it as described above
  3. Break out the save logic into an func Update() : err function to minimize the number of checks needed (just thought of this - think I like this one)

I feel like none of these are perfect; am I missing something obvious?

  • 写回答

2条回答 默认 最新

  • doumo0206 2019-03-02 07:10
    关注

    Update

    The way you are persisting can cause numerous problems:

    • you could incandescently mutate original object
    • you mix different layers in one method, it makes code very fragile
    • method, does too much

    I suggest, separate update and persistence. 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)
        }
    
    }
    

    Old answer

    In your example you use by-value receiver. In this case, you get a copy of the struct in the method, you free to modify that copy, but all changes will not be visible outside.

    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


    I would strongly recommend avoiding such a coding style. In case you REALLY need some of this kind, here is an example for inspiration:

    playground: 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条)

报告相同问题?

悬赏问题

  • ¥100 任意维数的K均值聚类
  • ¥15 stamps做sbas-insar,时序沉降图怎么画
  • ¥15 unity第一人称射击小游戏,有demo,在原脚本的基础上进行修改以达到要求
  • ¥15 买了个传感器,根据商家发的代码和步骤使用但是代码报错了不会改,有没有人可以看看
  • ¥15 关于#Java#的问题,如何解决?
  • ¥15 加热介质是液体,换热器壳侧导热系数和总的导热系数怎么算
  • ¥100 嵌入式系统基于PIC16F882和热敏电阻的数字温度计
  • ¥15 cmd cl 0x000007b
  • ¥20 BAPI_PR_CHANGE how to add account assignment information for service line
  • ¥500 火焰左右视图、视差(基于双目相机)