As someone who reads a lot more Go than I write, Go is awful to read.
Reading Go means that I keep having to read idioms (as posted in the article) and parse them back into the actual intent, rather than just reading the intent directly. But I can't even trust that, since the nth rewrite of the idiom might have screwed it up subtly.
Reading Go means that I can't easily navigate to the definition of a function, because it implicitly merges folders into a single namespace, so I have to try them one by one.
Reading Go means that I usually don't have tooling that can jump to definition, because there are umpteen different dependency management tools, and Gopls only supports one of them.
Reading Go means that even when I have tooling available, and it feels like working today, jump-to-definition becomes impossible as soon as interfaces are involved, because structural typing makes it impossible to know what is an intentional implementation of that interface.
>Reading Go means that I can't easily navigate to the definition of a function, because it implicitly merges folders into a single namespace, so I have to try them one by one.
Not sure what you mean here
>Reading Go means that I usually don't have tooling that can jump to definition, because there are umpteen different dependency management tools, and Gopls only supports one of them.
There's 1 dependency management tool though. It's go modules. Unless you live 2 years in the past?
>Reading Go means that even when I have tooling available, and it feels like working today, jump-to-definition becomes impossible as soon as interfaces are involved, because structural typing makes it impossible to know what is an intentional implementation of that interface.
You just right click in VS Code and click "Go to implementations". Or if you use Goland it's right there on the gutter.
Reading comments like this really makes me wonder if anyone complaining about Go has actually used it.
In Rust, if I see a call to `foo::bar::baz()`, I instantly know that `foo/bar.rs` or `foo/bar/mod.rs` (and only one will ever exist) contains either a `fn baz()` (in which case I'm done) or a `pub use spam::baz;` (in which case I can follow the same algorithm again).
In Go, if I see a call to `import ("foo/bar"); bar.baz()`, all I know is that ONE of the files in the folder `foo/bar` contains a function `baz`, with no direction on which it is.
> There's 1 dependency management tool though. It's go modules. Unless you live 2 years in the past?
As I said, 99% of my interaction with Go is reading the code that other people wrote. I don't make the decisions about which dependency management tools they use.
> You just right click in VS Code and click "Go to implementations". Or if you use Goland it's right there on the gutter.
I use Emacs, but VS Code still uses the same broken Gopls.
> Reading comments like this really makes me wonder if anyone complaining about Go has actually used it.
> In Go, if I see a call to `import ("foo/bar"); bar.baz()`, all I know is that ONE of the files in the folder `foo/bar` contains a function `baz`, with no direction on which it is.
This is a code smell for poor file organization in your "foo/bar" module - in a well organized project, it should be obvious which file in a module contains a given function[1]. Go doesn't force file=module paradigm (preferring the folder to be the basis), however, it doesn't preclude having one file per module, if that's what you prefer. If you're reading someone else's poorly organized code, you can always use grep.
1. Say, you have an `animal` module, the first place you check for `NewCow()` is `animal/cow.go`
> Yes, this is what that complaint was about?...This whole subthread was about reading other people's Go code.
So the complaint, restated is "Go doesn't prevent other people from writing bad code?" Am I getting you right? If so, well, I have nothing to say about that.
edit: I do actually have something to say. I just remembered having to work with an 82,000-line long Perl module file that would defeat any IDE. Fun times. No language can save you from poorly organized projects, whether the modules are file-based or folder-based.
I would say it's closer to "Go doesn't do a really simple thing that nudges people towards writing more readable code, while having basically no tradeoffs".
Considering the far more tedious tradeoffs that Go does force on users in the name of supposed readability (for example: the lack of generics), I'd consider that a pretty big failure.
I don't expect them to be perfect. I do expect them to try.
FYI: Go's lack of generics was not related to readability - the Go team didn't have a solution they liked, so instead of saddling the language with a half-assed solution forever, they waited for a more elegant solution. Also: the design of Go generics was recently approved (as in the past month).
It's no secret that Go is targeted at "programming at large". Go's design favors larger, fewer modules, how those modules are structured is left to teams/individuals. I may be lucky to work with a great team, but I always find the code where I expect to find it. When I'm starting from scratch, I utilize modules, <$VERB>er interfaces, structs and receiver functions: I cannot remember ever running into an ambiguous scenario where I'm unsure where a particular piece of code ought to go.
Like the parent, I read more Go than I write, and I have never seen a single-file-per-module (unless the entire project is one file). And sometimes it's obvious what file a function is from (like in your examples), but a lot of times it isn't. For example is `GiveFoodToCow` in food.go or cow.go? Maybe there are patterns that experienced gophers know, but that adds cognitive load. And would having a 1 file per module paradigm have made go any more complicated?
food.go and cow.go do not belong in the same (sub)module, IMO. That said, each team (and project) have unique sensibilities - consistency and familiarity help here. My gut feeling is that the "belongingness" of feeding a cow is closer to the cow than the food, unless your food.go is full of "GiveFoodToCow(), GiveFoodToChicken(),...GiveFoodToX()" which is gross. With that complexity, you're better of with a Feeder interface and implement Feed() in cow.go (and chicken.go, etc). If you cannot distill the logic to a single interface, you're probably better off with a receiver (or regular) function in cow.go, because having a list of GiveFoodToX() with different args in food.go is the worst possible design you could go with.
> And would having a 1 file per module paradigm have made go any more complicated?
I was being facetious. Under normal circumstances, no one should use one file per module in Go, but if Rubyists are feeling home-sick while writing Go, the option is available to them ;)
but if you see `Bar::baz` you can just go to the top of the file, and find the `use something::somewhere::Bar`, and then know what file `Bar` is defined in. In go, the import line only tells you what module/folder it is in, and you either need some tool or some intuition on how the module is organized into files to know what file to look for the definition in.
Let's go with "Go is easy if you use an appropriate IDE"
Emacs is great for many things, but Go was clearly designed with GUI IDE tooling in mind. So if you decide to forfeit that huge benefit, of course it'll be a sub-optimal experience.
> Go was clearly designed with GUI IDE tooling in mind.
Surprisingly not. One of Go's authors commented many times in the early days that Go shouldn't need an IDE, only a text editor. He even commented that syntax highlighting/coloring were unnecessary distractions.
there was talk early in the project about whether Go needed an IDE to succeed. No one on the team had the right skill set, though, so we did not try to create one. However, we did create core libraries for parsing and printing Go code, which soon enabled high-quality plugins for all manner of editor and IDE, and that was a serendipitous success.
To be fair, we then had to throw all of those "high-quality" plugins in the garbage where they belonged, and go with Microsoft's Language Server architecture instead to really have a usable Go IDE experience in something like Emacs or vim (GoLand from JetBrains is of course much better, and that might be using some of the built-in Go tools?).
The first one is an issue, not just for reading but also for writing; you'll find slightly similar ways of doing the same thing, and Murphy's (Real) Law kicks in "If there are two or more ways to do something, and one of those ways can result in a catastrophe, then someone will do it."
The rest of the issues seem to be tooling issues, not an issue with Go itself. There are tools out there that do the right thing (personally I'm old school and use gotags, warts and all), so I'd suggest get better tools that work for you (or write one if you can't find one you like).
> Reading Go means that I keep having to read idioms (as posted in the article) and parse them back into the actual intent, rather than just reading the intent directly. But I can't even trust that, since the nth rewrite of the idiom might have screwed it up subtly.
This. Also, this idea that simple(r) = easy to read just doesn't hold. Is brainfuck easy to read? It's very simple.
Even without being so extreme, we could simplify go replacing loops and structured ifs with "if-gotos". Or remove list enumeration as in range(my list) and only allow using integer indexing. Would that make go easier to read or reason about?
> Reading Go means that I keep having to read idioms (as posted in the article) and parse them back into the actual intent,
What? I never have to do this...
> I can't easily navigate to the definition of a function . . . I usually don't have tooling that can jump to definition
What? You command-click on it... ?
> jump-to-definition becomes impossible as soon as interfaces are involved, because structural typing makes it impossible to know what is an intentional implementation of that interface
What? You obviously can't jump-to-definition of an interface, but in what way is this a limitation?
Reading Go means that I keep having to read idioms (as posted in the article) and parse them back into the actual intent, rather than just reading the intent directly. But I can't even trust that, since the nth rewrite of the idiom might have screwed it up subtly.
Reading Go means that I can't easily navigate to the definition of a function, because it implicitly merges folders into a single namespace, so I have to try them one by one.
Reading Go means that I usually don't have tooling that can jump to definition, because there are umpteen different dependency management tools, and Gopls only supports one of them.
Reading Go means that even when I have tooling available, and it feels like working today, jump-to-definition becomes impossible as soon as interfaces are involved, because structural typing makes it impossible to know what is an intentional implementation of that interface.