在Golang中的全局变量

huangapple go评论92阅读模式
英文:

Global Vars in Golang

问题

我一直在阅读一本关于Golang的书,它常常使用全局变量来公开私有函数的结果。为什么不直接公开公共函数呢?这里有一个例子。
我来自Java,这对我来说有点棘手。使用这样的全局变量有什么好处呢?

编辑:我注意到,没有括号,我并没有调用函数。它只是一个指针。这是一种好的做法还是有一些优势呢?

package config

import "time"

var (
	GetHTTPServerAddress = getHTTPServerAddress
	GetHTTPReadTimeout   = getHTTPReadTimeout
	GetHTTPWriteTimeout  = getHTTPWriteTimeout
)

func getHTTPServerAddress() string {
	return getConfigString("http.server_address")
}

func getHTTPReadTimeout() time.Duration {
	return getConfigDuration("http.read_timeout")
}

func getHTTPWriteTimeout() time.Duration {
	return getConfigDuration("http.write_timeout")
}
英文:

I've been reading a book about Golang and it's common use global vars to expose the result of private functions. Why not to expose public functions? here's an exemple.
I am comming from java and this is tricky for me. What are advantages of using global vars like these?

EDIT: I notice that without parenthesis I am not calling the function. It's just a pointer. Is it a good pratice or does have some advantages?

package config

import "time"

var (
	
	GetHTTPServerAddress = getHTTPServerAddress
	GetHTTPReadTimeout = getHTTPReadTimeout
	GetHTTPWriteTimeout = getHTTPWriteTimeout
)

func getHTTPServerAddress() string {
	return getConfigString("http.server_address")
}

func getHTTPReadTimeout() time.Duration {
	return getConfigDuration("http.read_timeout")
}

func getHTTPWriteTimeout() time.Duration {
	return getConfigDuration("http.write_timeout")
}

答案1

得分: 5

我一直在阅读一本关于Golang的书,它常常使用全局变量来暴露私有函数的结果。

为了暴露私有函数的结果,而不是函数本身;你的例子有点不对。没有括号,你暴露的是函数本身。

package main

import (
    "fmt"
)

var(
    FooItself = foo      // (func() string)(0x108b720)
    FooResult = foo()    // "bar"
)

func main() {
    fmt.Printf("%#v\n", FooItself)
    fmt.Printf("%#v\n", FooResult)
}

func foo() string {
    return "bar"
}

试一试

你的例子应该像这样。

package config

var (
    // 你不会将变量命名为"Get*",那样会暴露实现细节。
    HTTPServerAddress = getHTTPServerAddress()
    HTTPReadTimeout = getHTTPReadTimeout()
    HTTPWriteTimeout = getHTTPWriteTimeout()
)

func getHTTPServerAddress() string {
    return getConfigString("http.server_address")
}

func getHTTPReadTimeout() time.Duration {
    return getConfigDuration("http.read_timeout")
}

func getHTTPWriteTimeout() time.Duration {
    return getConfigDuration("http.write_timeout")
}

但是并不需要私有方法。只需运行函数并将其赋值给全局变量。

package config

var (
    HTTPServerAddress = getConfigString("http.server_address")
    HTTPReadTimeout = getConfigDuration("http.read_timeout")
    HTTPWriteTimeout = getConfigDuration("http.write_timeout")
)

现在人们可以使用config.HTTPServerAddress,而细节仍然隐藏,无需额外的方法层。


是的,可以在公共函数的结果上这样做。一个非常常见的例子是错误代码。例如,在net/http中。

var (
	ErrBodyNotAllowed = errors.New("http: request method or response status code does not allow body")
  	ErrHijacked = errors.New("http: connection has been hijacked")
)

在Java中,你可能会为每种类型的错误创建一个异常子类。在Go中,你只需创建一个对象,将其作为全局变量共享,然后所有引用该对象的代码都会生效。

目的是隐藏细节。错误的确切措辞可能会改变,但只要每个人都引用net.http.ErrBodyNotAllowed,他们的代码仍然可以工作。


io/fs有一个更复杂的例子,它暴露了私有方法的结果。

var (
	ErrInvalid    = errInvalid()    // "invalid argument"
	ErrPermission = errPermission() // "permission denied"
	ErrExist      = errExist()      // "file already exists"
	ErrNotExist   = errNotExist()   // "file does not exist"
	ErrClosed     = errClosed()     // "file already closed"
)

func errInvalid() error    { return oserror.ErrInvalid }
func errPermission() error { return oserror.ErrPermission }
func errExist() error      { return oserror.ErrExist }
func errNotExist() error   { return oserror.ErrNotExist }
func errClosed() error     { return oserror.ErrClosed }

为什么在这里使用私有方法包装?为什么不直接使用ErrInvalid = oserror.ErrInvalid

(我猜)Go希望将oserror隐藏起来,甚至连文档中也不显示。

oserror只是创建错误对象来定义其可能的错误。

package oserror

import "errors"

var (
	ErrInvalid    = errors.New("invalid argument")
	ErrPermission = errors.New("permission denied")
	ErrExist      = errors.New("file already exists")
	ErrNotExist   = errors.New("file does not exist")
	ErrClosed     = errors.New("file already closed")
)
英文:

> I've been reading a book about Golang and it's common use global vars to expose the result of private functions.

To expose the result of private functions, not the functions themselves; your example is a little off. Without the parens you're exposing the function itself.

package main

import (
    "fmt"
)

var(
    FooItself = foo      // (func() string)(0x108b720)
    FooResult = foo()    // "bar"
)

func main() {
    fmt.Printf("%#v\n", FooItself)
    fmt.Printf("%#v\n", FooResult)
}

func foo() string {
    return "bar"
}

Try it.

Your example would look more like this.

package config

var (
    // You wouldn't call the variable "Get*", that exposes
    // the implementation.
    HTTPServerAddress = getHTTPServerAddress()
    HTTPReadTimeout = getHTTPReadTimeout()
    HTTPWriteTimeout = getHTTPWriteTimeout()
)

func getHTTPServerAddress() string {
    return getConfigString("http.server_address")
}

func getHTTPReadTimeout() time.Duration {
    return getConfigDuration("http.read_timeout")
}

func getHTTPWriteTimeout() time.Duration {
    return getConfigDuration("http.write_timeout")
}

But no private methods are necessary. Just run the function and assign it to a global.

package config

var (
    HTTPServerAddress = getConfigString("http.server_address")
    HTTPReadTimeout = getConfigDuration("http.read_timeout")
    HTTPWriteTimeout = getConfigDuration("http.write_timeout")
)

Now people can use config.HTTPServerAddress and the details remain hidden without requiring an extra layer of methods.


And yes, it can be done on the result public functions. A very common example is error codes. For example, in net/http.

var (
	ErrBodyNotAllowed = errors.New("http: request method or response status code does not allow body")
  	ErrHijacked = errors.New("http: connection has been hijacked")
)

In Java you might create an exception subclass for each type of error. In Go you make a single object, share it as a global variable, and everything references that object.

The purpose is to hide the details from the user. The exact wording of the error can change, but as long as everyone is referencing net.http.ErrBodyNotAllowed their code will still work.


io/fs has a more complex example exposing the result of its private methods.

var (
	ErrInvalid    = errInvalid()    // "invalid argument"
	ErrPermission = errPermission() // "permission denied"
	ErrExist      = errExist()      // "file already exists"
	ErrNotExist   = errNotExist()   // "file does not exist"
	ErrClosed     = errClosed()     // "file already closed"
)

func errInvalid() error    { return oserror.ErrInvalid }
func errPermission() error { return oserror.ErrPermission }
func errExist() error      { return oserror.ErrExist }
func errNotExist() error   { return oserror.ErrNotExist }
func errClosed() error     { return oserror.ErrClosed }

Why use private method wrappers here? Why not ErrInvalid = oserror.ErrInvalid?

(I'm assuming) Go wants to keep oserror hidden, even from the documentation!

oserror is just making error objects to define its possible errors.

package oserror

import "errors"

var (
	ErrInvalid    = errors.New("invalid argument")
	ErrPermission = errors.New("permission denied")
	ErrExist      = errors.New("file already exists")
	ErrNotExist   = errors.New("file does not exist")
	ErrClosed     = errors.New("file already closed")
)

答案2

得分: 2

如果这是一个函数的结果,那么它可能是类似于static成员初始化或者甚至是static final的东西。

你提供的示例表明这可能是某种配置加载过程,并且暴露了全局使用的值。

预计算数据也是常见的做法。

然而,如果你看到函数指针,可能是为了实现模拟。

具体情况可能有所不同。有时候这也只是一种反模式。

英文:

If it's result of a function, it's probably something like static member initialization or perhaps even static final.

The example you linked suggests that it's some sort of config load procedure and values are exposed for global use.

Precomputing data is also not unheard of.

If you see function pointers, however, it could be done to enable mocking.

YMMV. Sometimes it's just an antipattern as well.

huangapple
  • 本文由 发表于 2021年12月22日 07:01:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/70442475.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定