If you're unfamiliar with it, here's a short paragraph
package mylib
func init() {
defaultClient = new(42)
}
package main
import "mylib"
func main() {
mylib.Doit()
}
Don't do that. Whatever you need that init
function to do, make the calling package do it explicitly instead.
Scattering init
in your packages means a developer will need a lot of implicit knowledge to know their way around your codebase: something is happening somewhere setting up some initial state and you have no idea unless you know where to look. That developer bumbling around the codebase could be yourself, after spending months in another project and coming back to this one, not remembering a thing.
Being explicit in what the code needs and not rely on magic happening automatically helps a lot. No surprises. You can follow code simply and track down problems. Having init
somewhere.. that’ll be a fun day of debugging.
package mylib
- func init() {
+ func Initialize() {
defaultClient = new(42)
}
package main
import "mylib"
func main() {
+ mylib.Initialize()
mylib.Doit()
}
Assuming I've convinced you to not use init
, let me continue and say, don't just move an implicit init()
into an explicit mylib.Initialize()
too.
func main() {
mylib.Initialize()
mylib.Doit()
}
Whatever is going on inside mylib.Doit()
, it's a safe guess to say your Doit
function needs something to have been setup in some particular way or else it would fail. Correct?
func Doit()
But, looking at the function by itself, how does a developer know / remember to call mylib.Initialize()
before calling mylib.Doit()
?
Read every word in the function documentation? Step through the function source code? Hope they included mylib.Initialize()
when they copy-pasted how other places were using mylib.Doit()
? Wait for the test to fail and scratch their head? Wait for runtime errors and read about it on Sentry? Or worse?
Instead of blaming programmers for writing imperfect code, why not make such mistakes impossible?
One way to improve the function's usability is to make the implicit dependencies of our code as explicit function parameters.
- function Doit()
+ function Doit(client Client)
Now in order to use Doit
, the developer has to first find a Client
value somewhere. Various bad things can happen based on other details, but "forgetting to do something before calling Doit" is no longer a possible mistake.
Certainly there could be other approaches that you can think of.
Apply it recursively with a fresh eye towards usability and safety, you'll be surprised how many landmines can be eliminated; how many landmines there were.
If you haven't noticed, I'd like to point out that methods in OOP relies on "implicit dependencies": the state of the instance (all its attributes) can be used in a method.
func (object Thing) DoIt()
You'll never know, which object attribute (attributes? all of them? none of them?) are being used in a method call unless you step through the source code.
func (object Thing) DoIt() {
object.defaultClient.doIt(object.currentPage)
}
When you see some code calling the method x.DoIt()
, do you remember to ask, "is x.defaultClient
and x.currentPage
set?". What lies underneath the defaultClient.doIt
method?
How can we go about improving OOP methods to prevent the unnecessary mistakes we've discussed?