【问题标题】:Test for nil values in nested stucts测试嵌套结构中的 nil 值
【发布时间】:2015-03-28 16:44:26
【问题描述】:

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

然而,这个结构中的一些字段是“省略的”,所以我以一个可以在不同地方有空位的结构结束 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 语句会很痛苦。 哦,速度很重要。

【问题讨论】:

    标签: pointers go struct


    【解决方案1】:

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

    在您的示例中,BarBaz 结构被用作指针,所以用 getter 武装它们。重点是添加带有指针接收器的方法,首先必须检查接收器是否为nil。如果是,则返回结果类型的零值。如果没有,则继续返回结构的字段:

    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 上试试。

    【讨论】:

    • 非常好的建议(赞成)。我之前在关于 Go 的 null 安全运算符的辩论中看到了这一点:reddit.com/r/golang/comments/5eizbo/…
    • 我将接受的答案换成了这个答案,因为这是我最终使用的:很多访问器函数。不过,我可以通过使用您的 self==nil 测试来改进。
    • 我已经为 codegen 找到了这种方法的实现:google/go-github/github/gen-accessors.go
    【解决方案2】:

    如果你想避免'reflect'(反射,作为测试任何struct的字段的“通用”方式,有点像“Get pointer to value using reflection”或this gist:它更慢) ,最可靠的方法是在Foo 上实现方法以返回正确的值

    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 命令可以帮助生成其中的大部分。

    【讨论】:

    • 嗯,reflect 很好用也很通用,但是用起来很慢。我错过了 go generate,它看起来很有趣。将潜入。
    • @RickyA 是的,每次你看到:“写所有这些会很痛苦......<code>”,你现在有go generate的go 1.4选项:golang.org/cmd/go/#hdr-Generate_Go_files_by_processing_source
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多