如何优雅地处理gin / golang Web服务中的错误

时间:2018-05-31 16:18:03

标签: go

我正在使用gin编写一个简单的REST API。我已经阅读了许多关于使错误处理在重复性方面没有重复的帖子和文本,但我似乎无法理解如何在杜松子酒处理程序中执行此操作。

我的所有服务都是针对数据库运行一些查询并将结果作为JSON返回,因此典型的处理程序看起来像这样

func DeleteAPI(c *gin.Context) {
    var db = c.MustGet("db").(*sql.DB)
    query := "DELETE FROM table WHERE some condition"
    tx, err := db.Begin()
    if err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    defer tx.Rollback()
    result, err := tx.Exec(query)
    if err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    num, err := result.RowsAffected()
    if err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    err = tx.Commit()
    if err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    c.JSON(200, gin.H{"deleted": num})
}

正如你所看到的,即使这个简单的处理程序重复相同的"如果错误!= nil"模式四次。在"选择"基于API我有两倍的数量,因为在将响应编组到JSON时绑定输入数据和错误时可能会出现错误。有没有一种方法可以让它更干燥?

2 个答案:

答案 0 :(得分:4)

你可以使用帮助器稍微更多DRY:

func handleError(c *gin.Context, err error) bool {
    if err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return true
    }
    return false
}

用作:

err = tx.Commit()
if handleError(c,err) {
    return
}

这只会将错误处理行数从4行切换为3,但 抽象出重复的逻辑,允许您在一个中更改重复的错误处理放置而不是处理错误(例如,如果您想添加错误记录,或更改错误响应等)。

答案 1 :(得分:4)

我的正常做法是使用包装功能。这有一个优势(超过Adrian的答案 - 这也是一个很好的答案,顺便说一句)将错误处理留在更加Go-idiomatic形式(return result, err,而不是乱丢你的代码handleError(err)类型调用),同时仍将其整合到一个位置。

func DeleteAPI(c *gin.Context) {
    num, err := deleteAPI(c)
    if err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    c.JSON(200, gin.H{"deleted": num})
}

func deleteAPI(c *gin.Context) (int, error) {
    var db = c.MustGet("db").(*sql.DB)
    query := "DELETE FROM table WHERE some condition"
    tx, err := db.Begin()
    if err != nil {
        return 0, err
    }
    defer tx.Rollback()
    result, err := tx.Exec(query)
    if err != nil {
        return 0, err
    }
    num, err := result.RowsAffected()
    if err != nil {
        return 0, err
    }
    err = tx.Commit()
    if err != nil {
        return 0, err
    }
    return num, nil
}

对我来说(通常,对于Go编码员而言),优先考虑的是DRY上的代码可读性。在我看来,在我看来,我的版本更具可读性的三个选项(你的原创,Adrian&和我的),并且它们以完全惯用的方式处理错误,并且它们冒泡到顶级处理程序。如果您的控制器最终调用其他返回错误的函数,则同样的方法也同样有效。通过将所有错误处理移动到最顶层的函数,您可以在代码的所有其余部分中摆脱错误处理的混乱(除了简单的“如果错误!= nil {return err}”构造)。

值得注意的是,这种方法可以与Adrian相结合,特别是与多个处理程序一起使用,通过更改"包装"功能如此:

func DeleteAPI(c *gin.Context) {
    result, err := deleteAPI(c)
    if handleError(c, err) {
        return
    }
    c.JSON(200, gin.H{"deleted": num})
}