Hacker News new | ask | show | jobs
by pylon 1864 days ago
One of the reasons I love Go is that it's just so easy to read Go code written by other people on my team or third party libraries. There have been tons of cases where I'm working with another library and I just step into their code in my editor to understand how something works. And the code is always so easy to follow.

It's very rare that I come across weird patterns or someone trying to be very clever. Its always straightforward code.

4 comments

This is one of its biggest advantages to me as well. I have had to support many production systems, written in varieties of languages, over my tenure and in my experiences the systems that live the longest are those that are the most readable by new team members; people transition in and out and its important for them to learn quickly. And while a person's language experience matters to some degree, it is more the underlying domain that takes the most time. Languages that simplify on the number of solution patterns and focus on key ones being "idiomatic" helps to lower the mental load someone has to overcome to learning that domain. If you know C, Java, JS, etc. you can pretty quickly figure out how to read Go, and that matters.

Is Go perfect at this? No. I too would love to see some higher level functions exist for to help reduce boilerplate. For example, this proposal: https://github.com/golang/go/issues/45955 to add Filter, Map, etc. to slices. That seems like a practical set of functions to add to minimize boilerplate while at the same time not breaking away from simple idioms.

Once generics are released, anyone will be able to trivially write an Underscore or Lodash-like library providing filter, map, reduce, etc.
This is precisely what I’m afraid off. I don’t want library to become a mess of chained lodash style functions. The verbose style of writing your own loops is very readable
> The verbose style of writing your own loops is very readable

That depends entirely on what style of programming you're most accustomed to. I've primarily written functional code the past several years, and even in that relatively short time, it's become for me in general easier to read chained/piped/composed function expressions than equivalent loops.

But sure, I can understand if you don't want that flexibility in Go. It's a fair position if you want the language to be confined to a certain imperative style of programming.

However, it's interesting to note that even if one can do loop-based imperative programming in Haskell and OCaml if one so desires, it's not idiomatic. Presumably, same would apply in Go.

When there's only one way to do it, everyone is or soon will be accustomed to it.

I'm not a fan, but it fits with google's goal when creating the language.

Agreed. I think that's probably one of Golang's strengths (and I've used it a bit professionally several years ago). I can understand if they want to keep it hyper-standardized.

For devs who want a more functional approach, there are a number of alternatives, including Rust (which is not a fully functional language, but has many aspects thereof).

While generics will allow for such to occur, IMO if the "idiomatic style" is to only leverage it for specific patterns then it should not really hurt general readability for the most used frameworks/libraries. There will, of course, be some tradeoffs that authors will make, but overall I'm hoping it enhances readability, especially for the code my team and I write at least. Looking at the proposal, there is a lot of practical debate about naming, consistency with bytes methods, etc. and such honest debate keeps me optimistic.
Yup, Go is certainly easier to read than Python.
It's mostly due to Go being statically typed and having good editor tooling IMO. Doesn't excuse other atrocities in its design.
Not even close to being true. lol
With big python projects it's often difficult to figure out what code is going to be executed next due to inheritance and mix-ins.

With Go, it's usually pretty straightforward. Yes, there are interfaces, but they're used in places where people actually want the choice of code to be dynamic, rather than people just having fun building up crazy hierarchies in the name of DRY.

So, with Go I find it very easy to read because of high code locality and the ease of following to the correct destination with function calls.

For things that are already local, "ease of reading" is just code for "familiar". Go is strictly easier to read because tracing through is strictly simpler. Anything else is just spelling and those barriers disappear with only minimal experience.

This is true for particular types of python, but not true for most of the python code I work with, for example. I'll grant you "encouraging non-complicated architecture" is a good thing, but Go swings a bit too far in the other direction, for example `set(foo) - set(bar)` in python is an extremely expressive (and efficient) way to give me the things in foo that aren't in bar.

In go, you can either do this as a nested for loop, which is harder to read and n^2 instead of n, or explicitly loop over each iterator and convert to a map (which, to be fair, is exactly what python does too), and then write the map-diffing yourself, which is easy to screw up and should probably be thoroughly tested to ensure you didn't make any mistakes in your implementation.

In python its one line of clear, expressive, known-to-be-correct, code. Granted the generics proposal should do a lot to improve things here, but I disagree that go is "strictly easier", because there's often far more to trace through, as there is far less abstraction provided for you by the language, so instead of having to perhaps unwind a single complex expression that uses some advanced language features, you have to unwind the pseduo-reimplementation of those abstractions in the local project.

I think you can summarize the go flavour of "readability" as:

Fewer constructs for the price of higher Signal-to-Noise ratio.

Go eschews almost any kind of syntactic sugar that might be unfamiliar to you.

The endemic "if err != return err" issue is also highly indicative of this choice.

I really find that quite hard to stomach. For small apps, this can be acceptable, but for any Go app that gets large enough, this trade-off becomes untenable. You have to scan through too much visual noise to get a clear understanding what the code is doing.

Go is a language for writing microservices that handle http requests and/or client-side networking "clients" to an api served by said microservices. My guess is the tradeoff is okay in 95% of those scenarios.
In my experience very true and I dont even like Go (but use it daily for work for several years now).

I used to write Python. I was very happy writing Python but dealing with other peoples Python bullshit is not something I miss.

This has been my experience. I love the fact that the standard library itself is written in Go, so it's easy to see what exactly is going on and even learn some idioms that way. It's refreshing for me personally as I had a JavaScript background before learning Go
Aren't most languages these days self-hosted? Which don't have a stdlib written in the language itself?
Is the primary Python implementation self hosting? Ruby? JavaScript? PHP? Perl? It's common for compiled languages to be self hosted, but less common for scripting languages. (Not that Go is a scripting language, just pointing out that many popular languages aren't self hosting.)

Even beyond the question of self hosting, I would say that it's rare for much of a scripting language's standard library to be written in that language... especially performance critical parts of the standard library. There would just be too much performance left on the table.

The person you're responding to specifically mentioned they came from JavaScript, where the standard library implementations weren't written in JavaScript.

(PyPy is a notable exception to this rule of thumb... perhaps as part of its desire to prove how good the JIT is, it seems to be mostly written in Python, which is neat.)

Yet decades old dynamically typed language like scheme and lisp are all self-hosted, even their own compilers. I can bet you that quite a few scheme implementations will beat python in performance.
Though that might be easier, given that for example Scheme is doing much less dynamic dispatch at runtime.
Fair point, I supposed I was thinking of compiled languages like Go.
Various parts of Python's standard library are implemented in C for performance. E.g. see https://lwn.net/Articles/725114/ under "Optimizations"
Haha this must be ironic or Stockholm syndrome. With duck typing and channels, missing basic modern language constructions... it is definitely not known for readability.
huh? It's certainly not a pretty language, but it's definitely readable.

We use Ruby/Rails and Go pretty heavily at work it's much easier to jump in and grok Go code than rails (written by the same engineers mind you).

Go removes abstractions (to a fault sometimes) so, anecdotally, it's pretty damn easy to just read and figure out what's happening.

Go is extremely well known for its legibility. You’re way off base with this take.
go is far too verbose to be readable. I think its one of the least expressive languages out there.
Lack of expressiveness improves readability: in a team setting not everyone 'expresses' themselves in easy-to-parse ways. Code is written once, but read many, many times.

I used to work on a team that maintained a large Perl codebase: Perl can be both expressive and terse which decreases readability, especially at times when a colleague felt like expressing their cleverness/individuality in a 'brilliant' one-liner using obscure language features. When the clever code is buggy, you'd have to fix the edge-cases in an even more 'cleverer' way, or unroll it into a readable function.

Lack of expressiveness does not guarantee improved readability. It can only prevent abuse of language expressiveness that could harm reability.

But expressiveness, when used correctly, can also improve reability. Which is more readable?

  longNames := users.filter(x -> len(x.name) > 10)

  longNames := make([]User)
  for _, x := range users {
    if len(x.name) > 10 {
      longNames := append(longNames, x.name)
    }
  }
The only world in which the second example is more readable is one where developers never heard of filter, but have fully memorized (and are not confused by) all 3 variants of make(), the strange syntax of append (including all of its shared data pitfalls) and just got used to mentally parsing this behemoth pattern above as "filter".

This world is probably real in some corners of the wider world where Go is used, but it is not the only possible world, and I don't think it's a good one.

Both of those are equally simple to read as it is a very, very simple example. Simple examples are never the issue. The issue is when there are complex things involving edge cases and fiddly bits where there's a nasty hack in the middle of it that can't be removed easily or cleanly or both.

In theory there's no difference between theory and practice. In practice Go is a lot younger than Haskell, to take that as some kind of comparison, and there seem to be a lot more successful projects written in Go despite that. I like Haskell, I haven't even be bothered to learn Go yet on my own. But lets be sensible about readability. Simplicity almost always wins or at worst ties, as in the simple filter above.

Everyone writing serious Haskell tends to end up writing new additions to the standard library to make it work. Reading Haskell can be challenging on this basis. There absolutely is pyrotechnical showing off. I don't see this kind of issue at all in Go - for good or ill. I'd be astounded if Go is 5% of the fun of writing Haskell. I'd be more likely to choose Go than Haskell if I have a large project than needs to work in short-ish amount of time. (And when isn't the amount of time short-ish?)

Not to mention the item index, which is so often unused as in your example, as the first position in `for` loop bindings. Would it be so hard to have a `range_indexed` version that includes the index? Maybe I don't write enough Go code, but I also tend to think the first item conveys more importance which is why I often double take when I read Go for loops. "Wait why are they ignoring... oh yeah it's just the index".