Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to use global variables

Tags:

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")
}
like image 441
user2502761 Avatar asked Jul 24 '16 12:07

user2502761


2 Answers

One usual way is to use Method Value

Consider a struct type T with two methods, Mv, whose receiver is of type T, and Mp, 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

like image 50
VonC Avatar answered Sep 21 '22 06:09

VonC


Not all global variables are bad. In your case:

  • The global variable is in the main package and therefore only accessible by a single program. This is ok.
  • The global variable is initialized once and not modified thereafter. 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.

like image 44
Roland Illig Avatar answered Sep 19 '22 06:09

Roland Illig