• 6.2.2. 首选可变参数函数而非 []T 参数

    6.2.2. 首选可变参数函数而非 []T 参数

    编写一个带有切片参数的函数或方法是很常见的。

    1. func ShutdownVMs(ids []string) error

    这只是我编的一个例子,但它与我所写的很多代码相同。 这里的问题是他们假设他们会被调用于多个条目。 但是很多时候这些类型的函数只用一个参数调用,为了满足函数参数的要求,它必须打包到一个切片内。

    另外,因为 ids 参数是切片,所以你可以将一个空切片或 nil 传递给该函数,编译也没什么错误。 但是这会增加额外的测试负载,因为你应该涵盖这些情况在测试中。

    举一个这类 API 的例子,最近我重构了一条逻辑,要求我设置一些额外的字段,如果一组参数中至少有一个非零。 逻辑看起来像这样:

    1. if svc.MaxConnections > 0 || svc.MaxPendingRequests > 0 || svc.MaxRequests > 0 || svc.MaxRetries > 0 {
    2. // apply the non zero parameters
    3. }

    由于 if 语句变得很长,我想将签出的逻辑拉入其自己的函数中。 这就是我提出的:

    1. // anyPostive indicates if any value is greater than zero.
    2. func anyPositive(values ...int) bool {
    3. for _, v := range values {
    4. if v > 0 {
    5. return true
    6. }
    7. }
    8. return false
    9. }

    这就能够向读者明确内部块的执行条件:

    1. if anyPositive(svc.MaxConnections, svc.MaxPendingRequests, svc.MaxRequests, svc.MaxRetries) {
    2. // apply the non zero parameters
    3. }

    但是 anyPositive 还存在一个问题,有人可能会这样调用它:

    1. if anyPositive() { ... }

    在这种情况下,anyPositive 将返回 false,因为它不会执行迭代而是立即返回 false。对比起如果 anyPositive 在没有传递参数时返回 true, 这还不算世界上最糟糕的事情。

    然而,如果我们可以更改 anyPositive 的签名以强制调用者应该传递至少一个参数,那会更好。我们可以通过组合正常和可变参数来做到这一点,如下所示:

    1. // anyPostive indicates if any value is greater than zero.
    2. func anyPositive(first int, rest ...int) bool {
    3. if first > 0 {
    4. return true
    5. }
    6. for _, v := range rest {
    7. if v > 0 {
    8. return true
    9. }
    10. }
    11. return false
    12. }

    现在不能使用少于一个参数来调用 anyPositive