This is generally great advice, and aside from the package manager recommendations is still relevant today. I do take issue with a few things though.
1. Don't put your internal libraries in /pkg. /pkg has special GOPATH meaning as "compilation cache". Its not an actual name conflict, but why bother risking it.
2. This is just wrong:
> fmt.Printf is self-contained and doesn’t affect or depend on global state; in functional terms, it has something like referential transparency. So it is not a dependency. Obviously, f.Bar is a dependency. And, interestingly, log.Printf acts on a package-global logger object, it’s just obscured behind the free function Printf. So it, too, is a dependency.
stdout (and the buffer, and mutex on it) are exactly the same as the global log object. In fact, `log.Printf` is more or less just an alias to `fmt.Printf`[0]
3. I wish it had mentioned the functional options pattern in the part about constructors[1]
Functional options are controversial in the Go community. I don't like them personally. The idea of having a bunch of functions that exist only to mutate internal state on init is... an odd choice. Google code is riddled with this sort of style.
It's annoying to write client code for another reason: It's hard to discover. For the "term" example in the article, you can't sit in your editor/IDE and type "term." and get a list of setter suggestions, since everything in a single namespace. "term.Speed()" does not sound like an option to me. Function names are generally verbs, and having one called Speed() doesn't read well. And it causes issues if you want to have a type called Speed. Then it has to be term.WithSpeed() or term.SetSpeed() or something. Neither of which is really self-explanatory.
The fact that options are functions has other downsides. The moment you pack a option into a function, they've lost their ability to be introspected. You can't dump the options to a debug log before you invoke the constructor function. You can never ask an option function what it contains.
I prefer representing options using actual data:
type (
Option interface {
isOption()
}
Speed int
RawMode bool
SomeOtherSetting struct {
A, B int
}
)
Unfortunately, due to how Go interfaces work, you'll have to provide a dummy private method to tie the types together, but it's a small loss. (Go itself uses this pattern in a bunch of places, such as the compiler's AST.)
> Functional options are controversial in the Go community. I don't like them personally. The idea of having a bunch of functions that exist only to mutate internal state on init is... an odd choice. Google code is riddled with this sort of style.
I come from a background in functional programming, and having used functional options in Go for a while now, I completely agree. I've never seen an implementation of them that felt natural or idiomatic to use. They're incredibly awkward, and I basically always prefer the alternative approaches to the functional options style.
> 1. Don't put your internal libraries in /pkg. /pkg has special GOPATH meaning as "compilation cache". Its not an actual name conflict, but why bother risking it.
It's a little too late for this because it's pretty much an unofficial standard for library code :-) Just look at Kubernetes, Docker or many other major Go-based applications/tools.
Last I checked, pkg is going away as a compilation cache. As of Go 1.10, the compilation is in fact now stored elsewhere: https://golang.org/doc/go1.10
I simply love the go toolchain. The defacto standards for code formatting, linting and vetting, and the speed of builds and tests save tons of time. Both clock time and overall developer productivity.
I have a GitHub CI service that verifies the standard go tools pass on every commit.
The bot itself is naturally written in Go and executed in a Lambda function.
Everything is just so fast! You get feedback in seconds. Which keeps you in the flow of coding.
The code from go kit and [oklog](https://github.com/oklog/oklog) are great examples of idiomatic Go.
Unfortunately the community at large doesn't really follow the "no init"/"no package global vars", which can sometimes lead to bad experiences importing opensource Go libs.
I feel like go-kit is quite antithetical to the Go mindset...it presents a lossy abstraction as a means of future-proofing against eventualities that will almost certainly never be encountered
to be honest it strikes me as the sort of library that excites intermediate developers who tend to over-architect
Your "almost certainly never" is another organization's "certainly inevitable" or "already happened". Go kit's scope and applicability is pretty clearly enumerated in the documentation. And there's nothing lossy about its abstractions.
> the sort of library that excites intermediate developers
I find this true about nearly all microservices in Go. Microservices are much more useful in something like Node that can only take advantage of 1 OS thread per instance. Without containerization and load balancing in Node you wouldn't be able to scale.
Go on the other hand can efficiently utilize a nearly unlimited amount of threads as necessary with its scheduler. You're much more likely to over-architect if you don't keep this capacity in mind.
Search for "update:" in the article. There are three and the most recent references a post from June of 2017. It appears the author has modified it for developments he considers important. It would be helpful if he indicated the revision date near the top of the article.
> Well, it is on its way out, have you missed the news regarding vgo?
Dep will have a clean migration path to vgo, and the latter isn't really production-ready yet.
For now, I'd recommend using dep for daily use, until you have a compelling reason to switch to vgo, at which point the migration will probably be automatic.
vgo is at least phrased as being a proposal, not necessarily the future of Go [1]:
This post sketches a proposal for doing exactly
that, along with a prototype demonstration ...
I intend this post to be the start of a productive
discussion about what works and what doesn't.
Based on that discussion, I will make adjustments
to both the proposal and the prototype, and then
I will submit an official Go proposal, for
integration into Go 1.11 as an opt-in feature.
Of course, this is Russ Cox, so chances are that his proposal will carry more weight with his fellow core Go team than that of Dep's authors.
Sam Boyer's follow-up is interesting reading [2]. I get the feeling that despite their ongoing discussions, the Dep team was/felt ambushed by this move.
We use Dep. It's good, much better than the buggy mess that is/was Glide.
My only criticism is that "dep ensure" will actually parse the code to discover dependencies through import statements, which is also what Glide does. To me, this is antithetical to the purpose of a Gopkg.toml/lock file. In other words, Dep's full list of dependencies isn't actually in the Gopkg.toml file; it's a sum of Gopkg.toml and your code. That is confusing.
My desired behaviour:
* "dep ensure" should always used the lock file, nothing else, to install;
* "dep ensure -update" should update the lock file to what is specified in Gopkg.toml (and only that);
* "dep ensure -add" (which I think should be "dep add") should b required to add new dependencies to the Gopkg.toml file.
Aside: I wish Go projects weren't stuck with BSD style flags (-update instead of --update). GNU style is more common and arguably more practical. I applaud whenever a project (e.g. Prometheus, recently) finally sees sense and goes over to GNU flags.
I'm fairly certain you can use `--flag` with the standard flag package from stdlib: https://godoc.org/flag (in the section on command line flag syntax).
Though supporting `-flag` does remove the very nice combining of short flags.
The weird thing is if you -add a dependency before you import it, then you get a warning that the dependency isn't used in the codebase yet. Of course it isn't, I just added it! My IDE (IDEA) resolves the imports for me, so I can't add the import until after I add the dependency. Nice little catch-22.
1. Don't put your internal libraries in /pkg. /pkg has special GOPATH meaning as "compilation cache". Its not an actual name conflict, but why bother risking it.
2. This is just wrong:
> fmt.Printf is self-contained and doesn’t affect or depend on global state; in functional terms, it has something like referential transparency. So it is not a dependency. Obviously, f.Bar is a dependency. And, interestingly, log.Printf acts on a package-global logger object, it’s just obscured behind the free function Printf. So it, too, is a dependency.
stdout (and the buffer, and mutex on it) are exactly the same as the global log object. In fact, `log.Printf` is more or less just an alias to `fmt.Printf`[0]
3. I wish it had mentioned the functional options pattern in the part about constructors[1]
0: https://github.com/golang/go/blob/b77aad089176ecab971d3a72f0...
1: https://dave.cheney.net/2014/10/17/functional-options-for-fr...
Functional options are controversial in the Go community. I don't like them personally. The idea of having a bunch of functions that exist only to mutate internal state on init is... an odd choice. Google code is riddled with this sort of style.
It's annoying to write client code for another reason: It's hard to discover. For the "term" example in the article, you can't sit in your editor/IDE and type "term." and get a list of setter suggestions, since everything in a single namespace. "term.Speed()" does not sound like an option to me. Function names are generally verbs, and having one called Speed() doesn't read well. And it causes issues if you want to have a type called Speed. Then it has to be term.WithSpeed() or term.SetSpeed() or something. Neither of which is really self-explanatory.
The fact that options are functions has other downsides. The moment you pack a option into a function, they've lost their ability to be introspected. You can't dump the options to a debug log before you invoke the constructor function. You can never ask an option function what it contains.
I prefer representing options using actual data:
Unfortunately, due to how Go interfaces work, you'll have to provide a dummy private method to tie the types together, but it's a small loss. (Go itself uses this pattern in a bunch of places, such as the compiler's AST.)I come from a background in functional programming, and having used functional options in Go for a while now, I completely agree. I've never seen an implementation of them that felt natural or idiomatic to use. They're incredibly awkward, and I basically always prefer the alternative approaches to the functional options style.
It's a little too late for this because it's pretty much an unofficial standard for library code :-) Just look at Kubernetes, Docker or many other major Go-based applications/tools.
I have a GitHub CI service that verifies the standard go tools pass on every commit.
The bot itself is naturally written in Go and executed in a Lambda function.
Everything is just so fast! You get feedback in seconds. Which keeps you in the flow of coding.
Maybe some other gophers will find it useful:
https://www.mixable.net/products/bios/
[0] http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in...
It has a little more than Peter's post, but it's still not too opinionated :)
The code from go kit and [oklog](https://github.com/oklog/oklog) are great examples of idiomatic Go. Unfortunately the community at large doesn't really follow the "no init"/"no package global vars", which can sometimes lead to bad experiences importing opensource Go libs.
to be honest it strikes me as the sort of library that excites intermediate developers who tend to over-architect
I find this true about nearly all microservices in Go. Microservices are much more useful in something like Node that can only take advantage of 1 OS thread per instance. Without containerization and load balancing in Node you wouldn't be able to scale.
Go on the other hand can efficiently utilize a nearly unlimited amount of threads as necessary with its scheduler. You're much more likely to over-architect if you don't keep this capacity in mind.
I’ve used it and it works great (much better than glide, gb et al) but I don’t see it in the wild too often.
[1] https://github.com/golang/dep
https://research.swtch.com/vgo
Dep will have a clean migration path to vgo, and the latter isn't really production-ready yet.
For now, I'd recommend using dep for daily use, until you have a compelling reason to switch to vgo, at which point the migration will probably be automatic.
Go's dependency management story is starting to resemble JS's modules story :(
Sam Boyer's follow-up is interesting reading [2]. I get the feeling that despite their ongoing discussions, the Dep team was/felt ambushed by this move.
[1] https://research.swtch.com/vgo-intro
[2] https://sdboyer.io/blog/vgo-and-dep/
My only criticism is that "dep ensure" will actually parse the code to discover dependencies through import statements, which is also what Glide does. To me, this is antithetical to the purpose of a Gopkg.toml/lock file. In other words, Dep's full list of dependencies isn't actually in the Gopkg.toml file; it's a sum of Gopkg.toml and your code. That is confusing.
My desired behaviour:
* "dep ensure" should always used the lock file, nothing else, to install;
* "dep ensure -update" should update the lock file to what is specified in Gopkg.toml (and only that);
* "dep ensure -add" (which I think should be "dep add") should b required to add new dependencies to the Gopkg.toml file.
Aside: I wish Go projects weren't stuck with BSD style flags (-update instead of --update). GNU style is more common and arguably more practical. I applaud whenever a project (e.g. Prometheus, recently) finally sees sense and goes over to GNU flags.
Though supporting `-flag` does remove the very nice combining of short flags.
I think it’s the right choice for now. It was going to be the one true solution and many people have stopped working on the alternatives.
We’ll see where vgo lands, but dep is very practical right now.
https://gokit.io/examples/
Another interesting recent project is Truss which allows you to autogen go-kit handlers from protobuf definitions
https://github.com/TuneLab/truss/blob/master/TUTORIAL.md