Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Go is a programming language for teams, not primarily designed to impress individual programmers...

I agree with all points with the author. But working in teams, or even mutiple teams on the same software, then go solves a lot of problems for you..

Yeah its a dumb language, missing a lot of features. But there is only 1 way to program, dependency management is sane (no circular dependencies), and the language is build for readability, not for writing code fast and elegantly...

Often short dense code with complex types is just really hard to read for your overage joe programmer.

We're not all computer scientists here.

Go is designed to write maintainable server side programs that can utilize concurrency in computer these days. Therefore they left out generics... I hope its coming. I do wish error handling and generics where part of the language... And a tuple type indeed.



Go impedes its own readability by encourage or even requiring such huge volumes of code.

(And Go ain't very good at concurrency. At least they should have let you mark values as immutable, or communicated entirely via copying like Erlang.)

Yes, tuples are a really weird thing with Go. If they had completely left them, that would be bad but sort of defendable. But instead they give you a half-baked implementation of some of what tuples do with their 'multiple return types'.

(And multiple return types aren't even a good fit for signaling errors. For error-handling you want to return _either_ the result _or_ the error, in a way that the compiler can check that you handled the error.

Instead as far as the types are concerned they are always returning both the result and an error, and human reviewers have to make sure that they are checked properly.)


Go is incredibly readable I find. Yes, you tend to find yourself writing a lot of code because of the lack of generics, but that is being fixed as we speak. Generics has a draft and it looks nice from a Go developers perspective.

And Go let's you communicate by copying. That's what a Channel is. Pass a struct and that is copied. Pass a pointer and the pointer is copied. The thing it points to isn't copied for glaringly obvious reasons.

And what would you suggest is a good alternative for returning multiple parameters. Currently this basically forces you to handle any possible errors and results in software you can very easily reason about.

Not sure what you mean about human reviewers needing to check errors.


Every isolated Go piece is pretty readable. The problem is that getting most things done requires enough code that it's a lot of work to take it all in. Run all the for loops in your head. Etc. Higher level (and esp FP) languages like Haskell or Scala will be the opposite. That bunch of function compositions may take a little bit of work to digest, but once you understand it you understand a lot.

When people disagree on readability its often bc they mean two different things. We would benefit from different terms for readable-in-the-small and readable-in-the-large.


It is a lot like reading levels. Go feels like a grade 5 reading level. This means most people can read it. It also means complex concepts take a lot longer to explain because you have to avoid all sophistication.


Counterpoint: I find the go standard libraries (which are quite extensive) incredibly readable.


> Go is incredibly readable I find.

I'm the opposite, I don't find it readable at all.

I find Go too verbose to be readable.


Exactly this. I find that each line of go is readable. However actually trying to figure out what the code is trying to accomplish becomes a chore because I can't keep all of the lines in my head.

Maybe it is because what I am used to but I find I am often "pattern matching" loops into `map`s `filter`s and similar in my head. Of course doing this, and making sure that I didn't miss a side effect or condition that makes it not-quite-a-map takes a lot more brainpower than just seeing `slice.map(|path| path.basename())`.


> And Go let's you communicate by copying. That's what a Channel is. Pass a struct and that is copied. Pass a pointer and the pointer is copied. The thing it points to isn't copied for glaringly obvious reasons.

I'm not sure what those glaringly obvious reasons are. In Erlang, you just send a copy of the whole struct. Not some kind of references or pointers.

See eg https://play.golang.org/p/P3qUtFenp2q

But as I am saying, if you want to send pointers (or references) over a channel, they should support marking things as immutable.

> And what would you suggest is a good alternative for returning multiple parameters. Currently this basically forces you to handle any possible errors and results in software you can very easily reason about.

Go doesn't force you to handle errors at all. The compiler will happily mix up the branches of your `if` that checks for errors, or let you get away without checking anything at all.

My suggestion would be eg algebraic data types. Especially sum-types. Or in more C inspired terms: tagged unions plus pattern matching. Or 'an enum with parameters'.

> Not sure what you mean about human reviewers needing to check errors.

In a language without any checking at all like JavaScript, human authors and reviewers have to make sure that you don't accidentally eg add a string to an int. Or otherwise, have to make sure that at least you have enough test coverage.

In Go, the compiler can yell at you when you are trying to add an int to a string.

In OCaml or Haskel or Rust (or any language with algebraic data types), the compiler can make sure that you check your errors. So in eg Haskell syntax that looks like this:

    case someFunctionThatMightGoWrong(someParameter) of
        Error errorDetails -> handleErrors(errorDetails)
        Success someValue -> doSomethingSensible(someValue)
Crucially, `someValue` is only in scope in the branch where we match the right pattern. So you can't accidentally go on computing with that variable in the wrong branch.

Does this make sense? If not, I can try to explain in some other way.


Regarding the example in your first point, you are still sending a reference `m` of the map. If you wanted to send an immutable type, you could declare the channel to only accept struct values (and not pointers to structs) and you'll get your desired behavior.

Here's a playground for reference:

https://play.golang.org/p/U2tYZVTtUf-


Given that there is no way to make a non-pointer map in Go, there is no workaround for the example they gave.

The broader point is that there is no way in Go to ensure that data passed over a channel will not be modified concurrently without modifying the type of the data specifically for the channel use case.

A deep_copy() primitive would fix this.


Right, but they mentioned sending immutable structs which is why I gave the struct example. But you're right that every declared map is a just a pointer.

With respect to your second point, I see what you mean but I still don't think that's a negative of Go. You can pass in copied values without having to have a deep_copy() primitive (loop map values and copy to new map, dereferencing a pointer, etc.). Like other parts of Go, if you want to make your data immutable there is no syntactic sugar. You have to explicitly write it out.


> With respect to your second point, I see what you mean but I still don't think that's a negative of Go.

It is a negative of Go, because Go pushes you into the pit of failure by its design: it is much, much harder to do the right thing (send deep copies of objects over channels) than it is to do the wrong things (send pointers or shallow copies over channels).


You'd have to roll your own deep_copy by hand (or code generation) for every single data type.


Yes, either a deep_copy() or a way to declare things immutable. (Or both.)


> And Go let's you communicate by copying. That's what a Channel is. Pass a struct and that is copied. Pass a pointer and the pointer is copied. The thing it points to isn't copied for glaringly obvious reasons.

Copying large structs is not ideal, but passing a pointer to the channel means the other side of the channel can mutate that data. If Go had immutable types, you could pass a constant pointer, allowing the other end of the channel to read from the pointer but not modify it.

> And what would you suggest is a good alternative for returning multiple parameters. Currently this basically forces you to handle any possible errors and results in software you can very easily reason about.

What he's talking about is returning a union type. You have a single return value, it's type is either a valid result (i.e. a string) or an error. The compiler expects you to do runtime type checks (basically) to assert whether your return value is actually a value or not.

The explicit return values get clunky in some situations. If I have a function that processes an array of items, where each item can fail individually, in Go the cleanest way to handle that is to make a struct that holds a pointer to a value (so you can check if it's nil) and an error. And you return an array of those structs. In languages with Options, you can simply return an Option object, and let the upstream caller figure out what to do if it's an error.

Javascript/Typescript Promises are a similar, though less featureful, implementation of the same kind of idea.

Not sure what you mean about human reviewers needing to check errors.


> What he's talking about is returning a union type. You have a single return value, it's type is either a valid result (i.e. a string) or an error. The compiler expects you to do runtime type checks (basically) to assert whether your return value is actually a value or not.

Not quite. I am suggesting a tagged union. Not a runtime type check.

You'd check the tag.

Some language only support the runtime type-checked version of union types. That's a bit silly. For example, it makes it much harder to write a function that may either error out itself, or produce an error message as its bona fide value.


I think even more than the lack of generics, Go is difficult to read because it forces you to mix the error path with the success path in every function which can possibly fail, often making you bubble errors up the call stack manually.


Well, the onerous error handling is a symptom of the lack of generics.

Go has no facilities to would allow you to abstract away the repetitive parts of error handling.


The argument for Go being a simple language that naturally produces code everyone can easily grok comes up time and time again.

I have never gotten it. I find Go code somewhat easy to write, just just give up on trying to be concise or elegant, but not at all easy to read. The excessive boilerplate just makes me feel like my brain does not have the working memory to piece it all together.

The constructs are simple, true, but if you are trying to write anything non-trivial, then simple syntax will not make your problem simpler. It is just expressed in tens of times more lines than in Elixir, Haskell or even PHP. This takes a toll on the reader.

As an example - you can accomplish interesting projects with Lego. And it is easy to get going for sure. But at some point, when you are trying to do something non-trivial, it becomes a hindrance, despite it's simplicity. Hence "I built a life-size XYZ in Lego" becomes impressive in it's own right.

Oh, and Go modules is a story of a train wreck.


I agree! If people believe that Go is easy to read because it is simple they must love reading brainfuck as there are only a handful of simple operations!


In what way are Go modules a train wreck?



This is told so often told that people believe it is true

The lack of any type of abstraction is not great for teams. The need to write boilerplate code harms readability more than writeability.

Understanding what a single statement does is not important. Understanding intent is more important. And Go provides very few tools on it.

The reason for Go's popularity is mostly tooling. I like structural typing too. But other than that it is a very tedious language.


> other than that it is a very tedious language

And that, is precisely why it is a good language for teams.


Yeah it's hard to write weirdly complicated code in Go. Stick Java or C# in the hands of a team and before you know it you've got typed factory service factories and every abstraction known to man.


What higerorderma said above applies to this ^ comment as well. It is another of those nonsensical things that keeps getting repeated all the time by some Go programmers.

That line of thinking promotes workarounds over long-term solutions. If your team has people who are abusing tools (like types, abstractions etc), the solution is to teach them how to not do that. The solution is NOT to remove the tool itself! That is like saying that a drilling machine is easier to misuse so "only the hammer is a tool for teams". Ugh.

Go is awesome because of things it _can_ do (it has the speed of C and enough libraries to write full-blown webservices with it). Please don't encourage the whole marketing agenda of touting its shortcomings as advantages. It doesn't work. All it does is it turns people away from the language.


Why do you think it gets repeated by Go developers?

I was a C# developer writing all these abstractions like every other C# developer does. It's idiomatic C#. OOP exists, you're encouraged to use it to abstract and decouple code.

Even if you try and strip all that away, you'll never escape it because every library you depend on is written this way. For example, try understanding https://github.com/protobuf-net/protobuf-net like I once did. The cognitive load is huge.

This is C#, this is what having a huge toolset results in.

A simple language with simple code isn't a shortcoming. It's an advantage. Overly complex code is a shortcoming.

I liked writing heavily abstracted code in C#, that's what I used to do every day. But I never liked reading other people's code because without the intimate knowledge of the code it is a huge mental load. Onboarding new developers into a code base they are unfamiliar with is also very time consuming.

In Go I realised that the only abstraction you need is an interface. There's no "abstraction first" mentality like there is in C#. You can define interfaces at any point in time, whereas in C# you must define an interface first, and if you don't then you end up going the OOP route with more abstraction.

And because of this small toolset, reading other people's code is easy. It all looks the same.


> Why do you think it gets repeated by Go developers?

Not by all, only by a subset of Go developers (until very recently I was one as well, maintained a moderate-size webservice). I specifically said "some Go developers" in my comment because that is what happens (at GopherCons, meetups and so on).

I don't really have a lot of C# experience, but I have read the same story about C++ from lot of people. I think you will agree that abstractions are not a problem, abuse is. Now, I have worked on a C# project for a very short time where I have seen the problems you described. But I do not follow the thinking that a tool was abused and hence its the tool's fault. That is silly.

I was a Go developer for ~4 years where I struggled because I had to constantly spend mental effort trying to ignore the people in the Go community who, instead of focusing on what Go does well, spent all their time (in GopherCon, meetups and so on) rationalizing missing features from Go. Here I was - with this nice language that let me write webservices which are efficient and do not carry a heavy runtime (CLR, JVM etc). But it is severely missing features that can help you be more productive when programming. After all that, listening to people say "its feature, not a defect" just makes you lose all hope that things will get any better.

A better approach is what (thankfully) some folks from the Go team take. You will find blogs from the core team where it is explained that Generics aren't missing because they are outright bad. They are missing because the Go team had not yet[1] figured out how to do it the right way (TM). _That_ is fine, it is the truth.

Finally, you say that it just isn't possible to avoid writing complex code with a powerful language. Well, I have heard that as well, from the same people. But it isn't true, I have worked on a C++ project where I was careful to keep things simple[2] and had quite a few developers on the team (with only college-level C++ experience, most of them Go devs) contribute to the project without much trouble.

But hey, maybe it is personal preference then. I would rather use powerful tooling and put cognitive effort into keeping things simple - rather than using something else and feeling limited by it all the time.

[1] now they have, for the most parts [2] for example, it would use templates but avoid nesting more than one level, and so on. You get the idea.


I'm not trying to argue that C# is at fault by any means. But having a large toolset and trying to enforce very specific code quality when handling a code base touched by 100+ developers is very hard because using all these tools is very easy.

In Go it's quite difficult to make idiomatic overly complex code. If someone is making something really complex in Go I can easily see what they're trying to do and suggest an easier more idiomatic way of doing it.

In C# trying to understand some overly complex abstraction is a task in itself. Then trying to simplify it in a way that pleases everyone else who is of the mindset that these abstractions are good is another challenge. Most Go developers are of the opinion that there is only 1 or 2 idiomatic ways to solve most problems. But in C# there's so many more because of the more advanced language.

I'm not trying to justify the shortcomings of Go either. From day 1 of learning Go I was craving for generics and I read people saying "you don't need generics, just use interface{} and type switch.". It's ugly and it's one of the worst parts of Go, I absolutely hate seeing interface{} in a function.

But in the context of working in a team I've worked on sizeable projects in Go and bringing new devs of various skill levels onto the project has been a breeze. The code they write is the same as the code I write because it almost has to be, there's not many ways they can stray from the path laid out ahead.


I've worked professionally on C# and Go. I've seen overly abstracted C# code but I also have seen Go code that was clearly missing good patterns and ended up a ton of spaghetti. The difference that I see is that C# gives you the tools to do it well whereas Go lacks many tools to make a complex code base nearly as maintainable as C#.


> But I never liked reading other people's code because without the intimate knowledge of the code it is a huge mental load.

But that's exactly where Go projects end up at. Reading any 1-4 Go lines is easy. Trying to mentally decode a function of 70 lines almost never is. It is verbose as hell. And then you are like "a-ha! this goes through those two collections, filters one of them, maps the other and then combines the results through that algorithm". But you lost 20 minutes until you got to that conclusion.

Compare this with OCaml or Elixir for example where such a function would literally be 10-15 lines.

(It's not an unique disadvantage to Go, mind you, but we're discussing it currently. Other languages have the same problem, e.g. C# and Java.)


> Yeah it's hard to write weirdly complicated code in Go

Just because Go lacks many forms of abstraction, doesn't mean complex code will not be created with it. Quite the opposite. It's lack of expressiveness will encourage "frameworks", elaborate encodings, code generation and various productivity aids. Look at what the lack of generics has done to the Kubernetes code base:

"The core team replaced a compile-time language feature that was missing (Generics) with their home-built runtime system"

https://medium.com/@arschles/go-experience-report-generics-i...


> Quite the opposite.

No. What you see in K8S is the exception rather than the norm in Go - but is the norm in other languages.


> What you see in K8S is the exception rather than the norm in Go - but is the norm in other languages.

I suggest that is because not many large applications have yet been built in Go and it is still relatively young. If Go was used more sparingly as a "domain-specific language for concurrent network services", then I might be inclined to agree with you, but it seems to be marketed and promoted as a general purpose language.


Go is over a decade old and basically powers the majority of web infrastructure right now (k8s, docker, traefik, istio, terraform, cloudflare) and is used by google, Uber, twitch, SoundCloud, dropbox, YouTube, sendgrid... these are not exactly backyard projects. 90% of all software written falls into concurrent network services now.


Kubernetes is infamous for being one of the worst examples of a Go codebase in the community. And that proves my point really, this codebase sticks out like a sore thumb because it's no idiomatic Go.


> Often short dense code with complex types is just really hard to read for your overage joe programmer.

I think people make the mistake of measuring how long it takes you to read x lines, when really you need to measure how long it takes to read x functionality.

There've been times when I replaced a 200 line class with 4 lines of Scala. Those lines were very much the kind of complexly-typed code that people attack Scala for. It took time and effort to understand what they did. But they were still a lot more maintainable than a 200 line class.


I disagree with only 1 way to write programs in Go.

Go is not an old Fortran (see [1] about "the only real structure is an array") so there is always choice between array-of-structures and structure-of-arrays.

[1] https://www.ee.ryerson.ca/~elf/hack/realmen.html

Haskell (and C++ for that matter) can hide the difference with associated types (and templates).

PS Speaking about arrays - I once read Go's specification for some obscure fun and realized that I read a third of it and still am reading about arrays and stuff and not about programming.


> I do wish error handling and generics where part of the language... And a tuple type indeed.

On top of that I wish `null` wasn't and sum types + pattern matching were (and were used for error handling).


I agree with the first sentence, and that I found is one of the strongest points of Go. That you can look at your colleague code and be almost sure what it does, there just aren't hidden traps or clever hacks. It's always the same "dull" code, telling you in plain language what is happening.


I had an opposite experience. Go code is just like other code in any other languages. It still can be abused to be unholy mess. I see a Go code that have channels all over the place that it's hard to follow the program flow, a function that can panic deep down the stack when feed with certain input.


Channels for me make Go harder to read than most languages. Once you encounter a channel when reading Go code, good luck understanding the flow from then on.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: