Using method values to get rid of global variables #19

Open shurcooL opened this issue 7 months ago
shurcooL commented 7 months ago +

Have you ever had a piece of Go code like this:

// TODO: Get rid of the global variable.
var foo service

func handleFoo(w http.ResponseWriter, req *http.Request) {
	// code that uses foo
}

func main() {
	foo = initFoo()

	http.HandleFunc("/foo", handleFoo)
}

One way to get rid of that global variable is to use method values:

type fooHandler struct {
	foo service
}

func (h fooHandler) handle(w http.ResponseWriter, req *http.Request) {
	// code that uses h.foo
}

func main() {
	foo := initFoo()

	http.HandleFunc("/foo", fooHandler{foo}.handle)
}

Of course, net/http also has http.Handle so in this case you can simply change fooHandler to implement http.Handler interface:

type fooHandler struct {
	foo service
}

func (h fooHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// code that uses h.foo
}

func main() {
	foo := initFoo()

	http.Handle("/foo", fooHandler{foo})
}

But method values are great when you need to provide a func, for example in https://godoc.org/path/filepath#Walk or https://godoc.org/net/http#Server.ConnState.

Write Preview Markdown
megaserg commented 7 months ago +

If you have two or more global dependencies, would you put them in the same struct? I guess at this point it becomes a "context"...

Also, does this closure approach look simpler to you?

func bind(foo service) func(http.ResponseWriter, *http.Request) {
    return func(w http.ResponseWriter, req *http.Request) {
        // code that uses foo
    }
}

func main() {
    foo := initFoo()

    http.HandleFunc("/foo", bind(foo))
}
Write Preview Markdown
shurcooL commented 7 months ago +

Sergey, those are valid points.

If you have two or more global dependencies, would you put them in the same struct? I guess at this point it becomes a "context"...

Yep, I pretty much see it as a context. Similar to http.Client or build.Context.

I used this to clean up package scoped variables that were only needed by that specific handler, they weren't really global dependencies.

Also, does this closure approach look simpler to you?

This is yet another viable way to accomplish the same goal. I don't think I prefer it though, because the handler code ends up indented an extra level, which looks slightly messy. But in some cases it could work well. So thanks for sharing the idea!

Write Preview Markdown
to comment.