首页 > 解决方案 > Golang 最佳实践:空数组响应或错误?

问题描述

对于接受对象切片并返回另一个对象切片(理想情况下与输入数组长度相同)以及错误的函数,在错误处理方面的最佳实践如下:

func ([]interface{}) ([]interface{}, error)

一种方法是,每当您在处理切片中的一个对象时遇到错误时,您都会返回错误响应,但是在接收函数中,如果您不丢弃所有切片元素,错误响应就变得毫无用处,只是告诉我们认为处理其中一个元素或全部失败。另一种方法是在没有处理任何元素时返回错误,但我觉得这又没什么用。另一种方法是您不包含错误作为返回对象,而是对于每个切片元素结构,将其自己的错误对象作为复合对象,以便您可以将元素错误作为输出发送。

最好的方法显然取决于特定的场景,但是,我想知道人们是否遵循任何最佳实践或围绕这个问题的任何设计模式。

PS:这是最接近的问题之一,但是因为它接受单个对象作为输入,所以不是很相关: 返回空数组或错误

标签: arraysgoerror-handling

解决方案


...一个函数,接受[表示一个]对象数组的接口切片并返回另一个[表示一个]对象数组的[接口切片]以及错误...

你没有告诉我们足够多的事情继续下去。

  • 返回的切片实际上与参数切片有什么关系吗?
  • 如果有,他们有什么关系?例如,返回的切片可能应该是输入切片大小的一半,当且仅当输入对象的数量为奇数时才会发生错误,在这种情况下,最后一个输入对象已被忽略。
  • 输入必须按顺序处理,还是并行处理?

另一种方法是您不包含错误作为返回对象,而是对于每个数组对象结构,将其自己的错误对象作为复合对象,以便您可以将元素错误作为输出发送。

如果输出与输入是一对一的,并且您打算并行处理它们和/或在达到一个坏的输入时继续处理剩余的输入,这可能是一种明智的方法。等效地,您可以让输出切片包含错误。

这真的非常依赖问题。

编辑:考虑,例如,以下(我不认为这是好的,请注意):

const maxWorkers = 10 // tunable

// Process a slice of T's in parallel.  The results are either an
// R for each T, or an error.  Caller provides the actual function
// f(T), which returns R + error (an empty/zero R for error).
func ProcessInParallel(input []T, f func(T) (R, error)) ([]interface{}, error) {
    // Make one channel for sending work to workers,
    // and one for receiving results from workers.
    type Todo struct {
        i    int // the index of the item
        item T   // the item to work on
    }
    workChan := make(chan Todo)
    type Done struct {
        i int   // the index of the item worked on
        r R     // result, if we have one
        e error // error, if we have one
    }
    doneChan := make(chan Done)

    // Spin off workers: maxWorkers or len(input),
    // whichever is smaller.
    n := len(input)
    if n > maxWorkers {
        n = maxWorkers
    }
    var wg sync.WaitGroup
    for i := 0; i < n; i++ {
        wg.Add(1)
        go func(i int) {
            for todo := range workChan {
                i := todo.i
                r, err := f(input[i])
                doneChan <- Done{i, r, err}
            }
            wg.Done()
        }(i)
    }

    // Close doneChan when all workers finish.
    go func() {
        wg.Wait()
        close(doneChan)
    }()

    // Hand out work to workers (then close work channel).
    go func() {
        for i := range input {
            workChan <- Todo{i, input[i]}
        }
        close(workChan)
    }()

    // Collect results.
    var anyErr error
    ret := make([]interface{}, len(input))
    for done := range doneChan {
        i := done.i
        r, err := done.r, done.e
        if err != nil {
            anyErr = err
            ret[i] = err
        } else {
            ret[i] = r
        }
    }
    return ret, anyErr
}

这有一个整体错误返回,返回一个interface{}. 这意味着您可以立即判断一切是否正常。但是,使用起来有点烦人:

ret, err := ProcessInParallel(arg, f)
if err != nil {
    fmt.Println("some inputs failed")
    for i := range ret {
        if e, ok := ret[i].(error); ok {
            fmt.Printf("%d: failed: %v\n", i, e)
        } else {
            fmt.Printf("%d: %s\n", i, ret[i].(R))
        }
    }
} else {
    fmt.Println("all inputs were good")
    for i := range ret {
        fmt.Printf("%d: %s\n", i, ret[i].(R))
    }
}

为什么要打扰所有错误摘要?

相反,我们可以使用ProcessInParallelreturn []R, []error,例如,或者——可能更好——使用一个简单的error接口返回值来存储MultiError,正如Cerise Limón 在评论中建议的那样

ret, err := ProcessInParallel(arg, f)
if err != nil {
    if merr, ok := err.(datastore.MultiError); ok {
        // merr[i] indicates the various failed items
        // any ret[i] for which merr[i] is nil is OK
    }
} else {
    // all ret[i] are ok
}

一个不使用的工作示例MultiErrorhere

一个确实使用的工作示例MultiErrorhere


推荐阅读