测试嵌套结构中的 nil 值

Test for nil values in nested stucts

我在 go 中有一个深度嵌套的结构。这些是由 json 解组器构建的。

但是这个结构中有相当多的字段 'omitifempty' 所以我用一个可以在不同地方有 nills 的结构结束 op。

示例(真正的嵌套更深,更大:400 行结构):

package main

import "fmt"

type Foo struct {
    Foo string
    Bar *Bar
}

type Bar struct {
    Bar string
    Baz *Baz
}

type Baz struct {
    Baz string
}

func main() {
    f1 := Foo{Foo: "f1"}
    f2 := Foo{Foo: "f2", Bar: &Bar{Bar: "br2"}}
    f3 := Foo{Foo: "f3", Bar: &Bar{Bar: "br3", Baz: &Baz{Baz: "bz3"}}}

    fmt.Println(f3.Bar.Baz.Baz) //-> bz3
    fmt.Println(f2.Bar.Baz.Baz) //-> panic: runtime error: invalid memory address or nil pointer dereference
    fmt.Println(f1.Bar.Baz.Baz) //-> panic: runtime error: invalid memory address or nil pointer dereference    
    //so far so good, but

    //is there a more generic way to do this kind of testing?
    if f2.Bar != nil && f2.Bar.Baz != nil {
        fmt.Println(f2.Bar.Baz.Baz)
    } else {
        fmt.Println("something nil")
    }
}

问题是是否有更通用的方法来测试引用树中的某个节点是否为零?我需要得到很多不同的项目,写所有这些 if 语句会很痛苦。 哦,速度是个问题。

如果你想避免'reflect' (reflection, as a "generic" way to test fields of any struct, a bit as in "Get pointer to value using reflection" or in this gist:它更慢),最可靠的方法是在Foo上实现方法以便return正确的值

func (foo *Foo) BarBaz() string {
    if f2.Bar != nil && f2.Bar.Baz != nil {
        return f2.Bar.Baz.Baz
    } else {
        fmt.Println("something nil")
        return "" // for example
    } 
}

如果要编写很多这样的函数,也许go 1.4 go generate命令可以帮助生成其中的大部分。

一种优雅的处理方式(在我看来)是将 getter 添加到用作指针的结构中。 "technique" 也被 protobuf 生成的 Go 代码使用,它允许方法调用的自然链接,而不必担心由于 nil 指针导致的运行时恐慌。

在你的例子中,BarBaz 结构被用作指针,所以用吸气剂武装它们。重点是添加带有指针接收者的方法,首先必须检查接收者是否为nil。如果是,return 结果类型的零值。如果不是,则继续 return 结构的字段:

func (b *Bar) GetBaz() *Baz {
    if b == nil {
        return nil
    }
    return b.Baz
}

func (b *Baz) GetBaz() string {
    if b == nil {
        return ""
    }
    return b.Baz
}

带有指针接收器的方法的好处是您可以使用 nil 接收器调用它们。它不会导致运行时恐慌,直到您尝试引用它们的字段,而我们不会这样做,这就是为什么我们首先检查接收者是否为 nil(最终,接收者充当正常参数——这绝不是错误将 nil 作为指针参数传递)。

有了上面的 getter,使用就简化到这样了,在这些例子中都没有发生运行时恐慌:

fmt.Println(f3.Bar.GetBaz().GetBaz()) // naturally no panic
fmt.Println(f2.Bar.GetBaz().GetBaz()) // No panic
fmt.Println(f1.Bar.GetBaz().GetBaz()) // No panic

if baz := f2.Bar.GetBaz(); baz != nil {
    fmt.Println(baz.GetBaz())
} else {
    fmt.Println("something nil")
}

Go Playground 上试用。