The problems of Golang init function
Before we talk about the init function in Golang
, we should know what is a package
in Golang
. A go program is organized into packages. A package collected
some source files in the same directory. It worked like a box containing
some tools or a small machine in it. It is a starting point to
initialize the entire package. It seems to be right for the init function's purpose.
Suppose you had some code without init function like this:
//foo.go package foo var A int func bar(){} //main.go import("foo") function main(){ fmt.Println(foo.A) }
In this case, you import the foo
package, use the variable A without the
other parts. Everything is explicit. You might have a question, if I only use
the A variable, can I just import the A without other variables and functions
in this package? The answer in Golang is: No. You can't do this, you must
import the entire package, because it is a programming unit that can't be
divided. This code worked effectively, until the init function has joined this
game.
A package has several init functions might like this:
//foo.go package foo var A int func init(){A=1} func bar(){} //bar.go package foo var B int func init(){B=2} func bar(){}
As a package user, your code doesn't change, it still use the A variable only:
//main.go import("foo") function main(){ fmt.Println(foo.A) }
The package still worked. But the init function is running implicitly as you didn't know. In Golang, you must accept the init costing when you are the package user. It's simple, but the costing is not just the implicitly running, it will couple the entire package.
When you trying to write some unit tests, you can't forbid the init function. Especially if you initialize some external resources (such as databases, files, logs, or others), your unit tests would break down, they must load the resources even you just want to write a tiny unit test.
If you want your code working effectively, you should stop using the init function. Because the init function is global and you can't control its running timing. The worse disadvantage of init function is that it hidden the processing of a package and hard to know its running order, even you can write some test code to get the ordering.
The init function was not called by package user, it was called before the main
.
When an error has occurred in init function, what you can do? how to use the
only error mechanism (if err != nil
) to handle the errors? Maybe you can use
panic
in it, but how to use recover
to handle this panic? How to tell the
package users that they must ensure the package would not panic? How to
explain the package might panic on starting, even the package user just put a
line of import
in their code?
func init(){ f, err := file.Open(path) //how to handle the err? }
The above code will open a file path for writing or reading. When you running your code in the right path, everything is okay. But if your working directory has changed or you want to use some relative paths, how to handle the errors? That's why you should never put the code that might have errors in init function, and don't initialize the other package's resources in it.
pakage foo import "bar" function init(){ bar.Initlization() }
If you do this, your package would not be working independently. For cleaning your code, you should never put any other package code in init function. If the other packages need to initialize, they must give an initialization entry, or they must be initialized by themselves.
After thinking about the problems I'd met in init function, and read some
discussions about removing init
function in Go
. I got the best practice of using init function is:
Don't use.
There are several ways to avoid the init function.
If you have a global variable in package level, initializing on the declaration.
var( a = 0 p *foo= nil )
If the other package's resources need to initialize, or some extra resources need to initialize, use an exported init function.
package foo var ( f *os.File ) func InitFoo(path string) (error){ f, err := file.Open(path) _ := f return err }
If you want to ensure the init function must run only once, use the sync.Once.do
:
package foo var( once sync.Once f *os.File ) func InitFoo(path string) (error){ var err error once.Do(func(){ f, err = os.Open(path) }) return err }
If your package has several parts of the resource, and you want it can be initialized individually, using the old and boring Object-oriented programming.
//foo.go package foo struct Foo type{ } func NewFoo() (*Foo, error){ return &Foo{}, nil } //bar.go package foo struct Bar type{ } func NewBar() (*Bar, error){ return &Bar{}, nil }
If you still want to use init function in your code, the only advice is don't put any other packages calling in the init function, even it just a variable.
Removing init function will give your code more transparency and decoupling. Everything will work explicitly, the costing is visible and your code would be simple and easy to read.