Reputation: 563
I've heard many times that you should avoid global variables.
In my example I declared a global myTypes
variable only to avoid declaring that variable over and over again in a function call or something similar.
Is this how it should be done? Is there a better way? A more testable way?
var myTypes = map[string]string{
"type1": "tpl1",
"type2": "tpl2",
}
func AFunc(someType string) string {
fmt.Sprintf("this is your type %s", myTypes[someType])
}
func main() {
AFunc("type1")
}
Upvotes: 22
Views: 17010
Reputation: 777
You're not modifying myTypes
, just reading it, so it's not a variable at all, it's a constant, and would be defined as such if Go supported it (and make sure you don't mutate it -- unfortunately Go doesn't allow you to enforce "constness" like other languages do). Global constants are mostly fine.
If you were to modify myTypes
, e.g. by providing a function to add new types at runtime, then yes, it's a bad idea to retain myTypes
as global state. You might just get away with it as long as you do it only in your "main program" which you're sure will never be a package to be imported by other code, but you don't know where this code might end up / get copied to (or even just used from multiple places in the same package), so why risk it. If this becomes a package that gets imported by other packages, things may work fine as long as there's not more than one such "client" package is active at runtime, but as soon as somebody links together several such packages into one binary, all those user packages will stomp over each other's data in the global myTypes
. If a client package expects to only see the myTypes
that it put in earlier, this will break if there's another client with different expectations. So packages that work fine when used individually may break when used together, with no way to fix this except changing the shared code. So just don't do it. It's a shame that Google themselves use global state in some of their own public stuff, e.g. in the standard "flag" package and things like "glog" which uses it and thus inherits the problem. Don't do it.
Upvotes: 2
Reputation: 1326686
One usual way is to use Method Value
Consider a
struct type T
with two methods,Mv
, whose receiver is of typeT
, andMp
, whose receiver is of type*T
.
type T struct {
a int
}
func (tv T) Mv(a int) int { return 0 } // value receiver
func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver
var t T
The expression
T.Mv
yields a function equivalent to Mv but with an explicit receiver as its first argument; it has signature
func(tv T, a int) int
You can see an example of Method Value in this thread
// 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)
}
A new official approach for your global values is introduced in Go 1.7 with context.Context#Values
.
Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.
See "How to correctly use context.Context in Go 1.7"
Finally, in addition of being hard to test, global values can prevent vendoring.
See "To vendor or not to vendor, that is a question"
Many Go’s libaries have exported package variables. Those variables can be viewed as certain global states of a certain package.
Prior vendoring era, we can go get each imported package once and the global state of each imported package can be shared within all other imported packages.
Some devs may take it as granted and simply manipulate those global states at will.
However, with vendoring each imported package may have its own view of global states. Now a dev may found it impossible to change other package’s view of global state
Upvotes: 15
Reputation: 41676
Not all global variables are bad. In your case:
main
package and therefore only accessible by a single program. This is ok.On the other hand, whenever a global variable is modified during the program’s execution, the program becomes more difficult to understand. Therefore it should be avoided.
In a package that is meant to be reusable, global variables should be avoided since then two users of that package might influence each other. Imagine if the json
package had a global variable var Indent bool
. Such a variable is better to be hidden inside a data structure like JsonFormatter
that gets created anew each time someone wants to format some JSON.
Upvotes: 16