The problem is just that every line creates cognitive load and there's a tradeoff between concision and descriptiveness.
A language with piles of syntactic sugar and magic gets it wrong with too much concision and can read like line noise when it gets overused.
Go goes the other way though and makes it way too verbose and just makes it difficult to read the code. When a method needs to have 6 different error handling clauses in it, then it isn't as clear that 5 of those just bubble up the error while one of them has some unique handling. It also increases the chances that some programmer copypastas the boilerplate bubble-up code to all six of those cases and it sails through PR review. You can write a static analysis linter to force programmers to always handle errors and not ignore them, but you can't force them to handle errors correctly. When humans are reading the code, concision helps and verbosity hurts -- up until that crossover point where magic causes readability to suffer.
Go programmers seem to focus on abhoring magic and rejecting the benefits of concision. But when it comes to PR review your job is to stare at the whole method (or the whole file) and be able to "see" the bug, and more lines of code will make this job more difficult (and is also why some of the recommendations of the "clean code" book are pretty bad since extracting more tiny little methods can harm overall readability). There's a happy optimum somewhere where cognitive load is minimized. That isn't attained though by just having the simplest language design possible and offloading complexity into more verbose code.
But if you are wondering about Error cases, they are there to see when you're looking for them because that ? while unobtrusive is something you can look for.
I'm sure in most IDEs you could have it highlight ? in a "Looking for error handling" mode if that's what you want.
Note that Rust does not consider control flow to be something the core language owns exclusively, you can return core::ops::ControlFlow to say actually I also have an opinion about whether you should keep going, this can make sense for a closure or function intended to be called inside an iterator or other loop context. Some of the ergonomics for this aren't finished, but what is there is already useful where a Result would work but is ugly because your early exit scenario isn't in fact an error at all.
Error handling is no less important than the happy-path.
You just did prove my point though which is that this is the only argument that Go programmers consider, and they blindly reject that adding more lines of code can harm readability.
Can we lower the rhetorical temperature a notch? Just because someone disagrees with you doesn't mean they're "blindly rejecting" your reasoning. In particular, I'm not just a Go programmer--I've used Java, C#, Python, JS, C++, and C in various professional over the course of my career and I've also played around with dozens of other languages and I have more experience with several of those languages than I have with Go. My opinions are shaped by those other languages at least as much as they're shaped by Go, and indeed I didn't start out having these "pro-Go" opinions--rather, I adopted them over time after allowing my preconceptions to be challenged. Note also that some of my preconceptions haven't changed--I still think sum types and enforced handling of return values are a good idea, for example.
This expression
let a = x.iter().filter(...).apply(...).map(...);
is equally or even potentially _more_ cognitively complex than this expression for _, v := range x {
if !filter(v) {
continue
}
vv := apply(v, ...)
vm := map(vv, ...)
...
}consider it from a blocks-of-code metric, or some better slightly more abstract metric, that isn't affect by simple things like whitespace transformations, and try assuming that we all understand that we should write code that isn't monstrous to begin with.
I would personally much rather maintain the code in the second example than in the first.
In my experience, people can't actually read everything on the screen at one time anyway, and the more dense/terse things are the harder it is to read (otherwise we would minify everything).
> I'd much rather the program crash by default than attempt to continue with corrupt data by default.
It's not likely that it will continue with corrupt data because you can't use the return value without explicitly ignoring the error. It's not perfect, because there are cases where you want to crash when there is an error but no return value, and Go doesn't help you here. I would like to see this improve, but it's relatively low on my list of qualms with Go (I would rather have sum types, for example). It certainly isn't worth changing languages over especially since, in practice, Go seems to have fewer error handling bugs than exception-based languages.
Whether or not you can read everything on the screen at one time is missing the point entirely. The point is that context matters, and the more frequently you have to scroll to find it is more cognitive burden.
> It's not likely that it will continue with corrupt data because you can't use the return value without explicitly ignoring the error.
It is far too easy to accidentally do the wrong thing with an error in Go. In Rust, for example, no matter what you want to do with the result of a fallible call, you have to do it explicitly. If you want to crash on error, you `.unwrap()`; if you want to bubble it up, you `?`; if you want to continue with a default value, you `.unwrap_or()` or one of its variants.
> in practice, Go seems to have fewer error handling bugs than exception-based languages
This is based on?
And I disagree. Scrolling IMO is a lot easier than squinting to parse dense code. We have visual structure (indentation blocks and so on) for a reason. The visual structure aids in readability, and indentation blocks help the eye scan quickly over a document. The visual structure in most languages resembles control flow, except some languages make an exception (no pun intended) to this rule for error handling paths which are not easy to see at a glance.
> This is based on?
My experience.
This is a false dichotomy - there's a third option, which is not squinting (because, presumably, you're doing so because you decreased your font size), and being able to see more on the screen at the same time.
Moreover, scrolling is bad for cognition. It's pretty well-known that the human brain likes to use spatial maps - that's the reason why memory palaces are so effective. Scrolling decreases the ability of the brain to make spatial maps compared to, well, not scrolling.
> The point is that context matters, and the more frequently you have to scroll to find it is more cognitive burden.
This is not something you can "disagree" on - divorcing information from context always leads to more cognitive burden.
It's not a false dichotomy. Visual structure (via whitespace) comes at the expense of strict information density (assuming a fixed font size). If this is not true, then we would never have any (syntactically insignificant) whitespace.
> This is not something you can "disagree" on - divorcing information from context always leads to more cognitive burden.
Agreed, but this supports my point. It's a lot easier to scroll and scan visual structure than it is to reparse dense code. Density divorces us from context a lot more than physical distance on a screen.
You mean "unreadable code divorces us from context". "Density" doesn't have anything to do with it until you get to the point where your code is so dense as to become unreadable.
Moreover, "physical distance on a screen" is a strawman. The options aren't density and distance, they're density and not being able to see the code on the screen at all - between which, density is objectively better.
Seeing context is always better than not seeing context, assuming equal readability. Go's verbosity is both less readable and less dense than that of other, better-designed languages.
As density increases, the difficulty of parsing also increases. At a certain, relatively early point, that difficulty rapidly exceeds the costs of scroll-and-scanning.
> Moreover, "physical distance on a screen" is a strawman. The options aren't density and distance, they're density and not being able to see the code on the screen at all - between which, density is objectively better.
Well, we know density is not "objectively better" because scrolling exists (granted, if you have a hard requirement on a code editor that doesn't allow for scrolling, then you should definitely stick with the densest language you can find), and a little scroll-and-scanning is better than parsing dense code.
"Relatively early" is an unquantifiable statement, but regardless, that point is very far away from Go's design & generally accepted style, so this statement isn't really relevant to the conversation.
Regardless, scrolling exists by necessity, because some things simply can't fit on a single screen. It's still clearly always better to not scroll than scroll, assuming you aren't packing things in super tightly - I shouldn't have to provide evidence for this, but the fact that people don't just randomly clip text so they can add scrollboxes everywhere should be sufficient.
This is all a distraction from my last statement in my previous comment:
> Seeing context is always better than not seeing context, assuming equal readability. Go's verbosity is both less readable and less dense than that of other, better-designed languages.
>
> This is based on?
By explicitly annotating functions as fallible the language hints to the programmer that errors need to be accounted for.
With exceptions, the hints only appear at runtime - when your program crashes. There's nothing that nudges you towards handling errors at the point of writing code, so you end up with brittle software.
I believe a language where instead of subtypes you would get algebraic data types and could optionally mark whether a given exception is checked or not would be the ideal solution.
I'm guessing you already know this, but for anybody else reading - this isn't entirely accurate; Result::Err doesn't auto bubble up like an exception, you have to manually bubble it up. The "special sauce" comes from (A) the compiler forcing you to notice this and do something about it, and (B) the `?` syntactic sugar to make that super easy.
It does occur to me as I'm typing this that you might be talking about panics, though, in which case yeah that's entirely accurate.
meh, my ide squashes short if `err != nil` clauses (goland, but I've seen other editors/ides/golang plugins do this as well ), also I run a vertical monitor. It's just not enough of an issue to care about. I've seen similar features for editors for other languages that have features or patterns that also create a lot of 'extra bullshit that takes up screen realestate'.
I'd take exceptions any day of the week over Go's solution. I'd much rather the program crash by default than attempt to continue with corrupt data by default. I'd rather have concise, explicit, compiler-required error handling than exceptions, though.