w, err := b.NewWriter(ctx, "gopher.png", nil)
...
_, err = w.Write(data)
...
if err := w.Close(); err != nil
Why is := used on the first line but = used on the second line, and then := used again on the third line? I get that you use := when introducing a new variable, and = when just assigning, the third line is not really introducing a new variable, since err is still in scope... or is it?Not to mention, what is the last argument `nil` supposed to represent? In most high quality Node.js libraries, functions/constructors will take an optional options object so you can omit it, or pass an object like { timeout: 300 } to it.
The "if" block creates a new scope.
It's equivalent to
{ // begin new scope here
err := w.Close()
if err != nil {
foo
} else {
bar
}
}I also hate the inconsistent behavior when you check multiple errors in a row, the first one will get := but not the second one (if you don't put those in a if statement, which I never do). But now if for some reason, you remove the first := statement, the code will error on the second one, that needs to be changed from = to :=
A lot of it has to do with expectations. Going beyond the principle of least surprise, there's also a principle of "no dismay." Go is like the dependable old Corolla. It sets modest expectations, and it delivers a high degree of utility very consistently, and if you're ever let down, it's very rare. C++ can cause you utter dismay due to rather subtle slip-ups. I've only seen this level of dismay in Golang around subtle resource release issues around prepared statements.
This is true, but Go does have a clean and mostly familiar syntax, by design: you can mostly follow along with a piece of Go code even if you haven't learned the language.
I think that what the OP is pointing at is a related issue: readability. This is explicitly a high-priority design goal for Go: lots of things in the language are designed so that developers can easily read and understand the code in the projects that they work on, and that memory use is never hidden.
First, repetitive error handling makes it overly tedious to actually get a quick intuition for what a function is doing. With Rust as a comparison, it’s extremely easy to see what the happy path is trying to accomplish without having the downsides of arbitrary and unexpected exceptions. Go is inarguably worse here.
Second, the inability to express higher-level concepts (due to, yes, lack of genetics) seems like it forces every function to deal with the nitty-gritty details of whatever it’s trying to accomplish, rather than being able to express ideas at a higher level and deal with the specifics in smaller code units. Yes, you don’t get “surprising” behavior, but the cost of this is that you’re forced to keep both the high level goals and the low level details in your head simultaneously, which I find to be a massive hindrance.
As sort of a side effect of both of these, there ends up being a ton of repetitive boilerplate. This actually hides bugs since it’s easy to miss minor differences between overly similar blocks of code dealing with errors or looping. As an example, languages like Rust and Ruby that have proper functional-style iterator methods prevent so many bugs through their inclusion it’s absolutely baffling to me how someone would opt to design a language today without such things.
All languages are going to suck, somewhere. The trick is to actually optimize a language for the difficult, expensive parts of development. Many languages seemingly optimize for writing things from scratch slickly. They're "blog example optimized." Instead, languages should be optimized for debugging, reading code, and deployment.
The fact that Go is not afraid of boilerplate is often actually a good thing. I've had to debug C++ in the confluence of 2 templates, such that there was no source code to display in the debugger. I don't expect to have this problem in Go, ever.
"You can't pay people enough to carefully debug boring boilerplate code. I've tried." (Yaron Minsky of Jane Street)
While C++ goes to one extreme, not having certain expressive means (whatever they may be) has a price, and for certain kinds of problems, this price may be high.
This returns to the idea I very much agree with: "The trick is to actually optimize a language for the difficult, expensive parts of development." The catch is that difficult parts are also problem area-dependent. Go definitely found a sweet spot for certain kinds of problems, though.
Question is, how it will move beyond that, if ever.
I still think that JVM + JIT is hugely important, though. Now it's importance is somehow diminished by presence of LLVM, but 20 and even 10 years ago the situation with native compilation was different. (I know that the canonical Go compiler does not use LLVM.)
When Java came around, latest Oberon variant was Active Oberon (Active Objects are similar to goroutines), then we had Eiffel and Modula-3 as well.
Hence why I say it should have been Java 1.0.
There's boilerplate, and then there's boilerplate. If boilerplate is just a formality, with clean, competently designed underlying concepts, then it should be fine. Someone smart will build a code generator for that part of the project, you have your type safe-whatever and the project goes on its way. On the other hand, if your code base is just numb-nuts and people are just cut & pasting lava-flow code all over the place and rampantly putting business logic into ORM routines, then yes, that's going to impact hiring.
not having certain expressive means (whatever they may be) has a price, and for certain kinds of problems, this price may be high
This is also true. Even fairly innocuous boilerplate can build up to the point where refactoring can become tedious. There is no utopia, only constantly changing cost/benefit tradeoffs.
I do love how opinionated Go is, though. Every language should have a gofmt equivalent.
- not knowing if "Foo" being returned is a struct or an interface, so you're not sure if you should "if foo != nil" (it could not be nil if it's a struct)
- channels freezing (specially unbuffered ones). I still can't figure out how to debug those well.
- (the most common of all) spawning goroutines on a for loop, and all of them closuring over the same value (instead of one per item on the loop)
it's a surprising amount of sharp edges, for such a tiny language...
2. Always limit the scope of your channels. I like using unidirectional channels with structs that have more unidirectional channels. (For instance, a chan of workRequest{workData, returnChan} where return chan only receives responses for this request and is closed when no values are left)
3. I think you've been treated to well by JS closures, always hand over arguments you're going to use in the goroutine, this also allows the CG to clean up the stack of the function that started the closure goroutine.
2. Yes, I know how to “properly” do it. Does not eliminate human error.
3. Closures in any other language, afaik. I rarely code in Js, fyi
Most of our Kubernetes code looks something like this
_, err := clientInterface.Update(object)
if err != nil {
if machinery_errors.IsNotFound(err) {
//handle nil case
} else
// handle unexpected case
}
}Your people can pick up go reflect package or go routines/channels and co in 1-2 weeks and start contributing? impressive... most people can pick up for loops, if,switch statements, variable and function declaration in a week in any language as well...
In fact, a lot of non programmers use JS regularly to write scripts in the browser. I imagine your people know how to program before hand... So what do you think takes longer to learn? mastering go threading model or being able to add interactivity to the DOM?
It is definitely easier to write well than js IMO, and harder to make a big ball of mud in.
For me, the go threading model is easier. I just can't stand DOM and web programming in general. Currently working on a problem doing some computation across dozens of cores on multiple servers and go makes it a lot easier to spawn the 1000 goroutines and coordinate between them. I guess everyone has different interests and perhaps it is similar for those we hire.
Because you didn't take the time to learn the DOM, that's all. I doubt learning a simple composite API made of dumb nodes is more difficult than writing thread safe programs in Go. I'm not talking about tastes or interests, I'm talking about ease of learning.
Go isn't 'easy to pick up'. For loops, if statements and co are basic, and they are in all languages, that's my point. There is nothing particularly easy with Go.
A lot of what makes anything hard to learn are the non-obvious gotchas. The design philosophy of golang is aimed at avoiding the non-obvious gotchas and the creep-up-on-you-at-scale gotchas.
I'll take Scheme for a spin if I am doing something for fun.
EDIT: %s/bored/boring/g
func setupBucket(ctx context.Context) (*blob.Bucket, error) {
sess, err := session.NewSession(&aws.Config{
Region: aws.String("us-east-2"),
})
full of ( ) { } * and & as if we were still in the 80s. So Go is a very surprising language, but this is highly subjective. No hard feelings.I can't see any surprises here.
Contrast this with a language like Ruby which tries very hard to make punctuation optional, and often ends up with ambiguous cases where you aren't 100% certain how the interpreter will parse some code and so you end up throwing in all the punctuation anyway.
---
This is a valid Go program.
import "fmt";
func main() {
fmt.Println("Hello world")
}
This is an invalid Go program. import "fmt";
func main() {
// fmt.Println("Hello world")
}
Surprising.---
Creating typed general-use data structures is impossible.
Surprising.
Go is a thoroughly "unsurprising" language.