4 ms·
>simply because you have to think about error handling. until you write x, _ := some_code () Besides, Go errors are used in pair with the return value, so
by ernst_klim 8y ago
>simply because you have to think about error handling.
until you write
x, _ := some_code ()
Besides, Go errors are used in pair with the return value, so you have to check 4 possible combinations of value/nil and error/nil instead of 2 due to the absence of sum types.
Oh, and no generics, so your errors are either string or you have to check the interface in runtime which and can't statically check which exact errors could your code return.
Oh, and no monadic chains/exception mechanism, so feel free to write
x, err :=
if (err != nil) {
return err
}
y, err = ...
if (err != nil) {
return err
}
instead of
do x <- fun1 ()
y <- fun2 ()
...
and then just check the proper subset of possible errors, carefully inferred by the type system.
There are a bunch of different possible approaches for error handling: exceptions, monads, erlnags failures as possible case, Go crowd had just invented the worst and least error-prone one.
- weberc2 8y ago> until you write Yeah, you can definitely opt out of it and there are even cases where the compiler doesn't guarantee that you're handling the errors, but assuming you're making a good faith effort to adhere to conventions, then you're thinking about your errors. > Besides, Go errors are used in pair with the return value, so you have to check 4 possible combinations of value/nil and error/nil instead of 2 due to the absence of sum types. No, you don't. If the error is not nil, there's an error. Sum types would be nice, but they wouldn't reduce the number of checks, they'd just formalize the idiom. > Oh, and no monadic chains/exception mechanism, so feel free to write I'm fine with this. Monads are hard for people to understand, and I'm not playing code golf... > Go crowd had just invented the worst and least error-prone one. I don't think it's the least error-prone, but it's not bad.
- ernst_klim 8y ago>If the error is not nil, there's an error. And if the value is also not nil? Or if both value and error are nil? >Monads are hard for people to understand How are they hard if they are nothing but a mere formalization of a computation step? >I don't think it's the least error-prone Sure, typeless errors which you have to check in runtime, which are so tedious that people usually either ignore them or write something like that [1] are the least error prone error handling mechanism possible (beyond having no errors at all) [1] https://news.ycombinator.com/item?id=18732485 https://news.ycombinator.com/item?id=18732485
- weberc2 8y ago1. If there is an error, it doesn’t matter what the value is. 2. Monads are famously difficult to wrap one’s head around. 3. Either you’re a big fan of Go’s error handling, or you’re confusing most and least. I’m guessing it’s the latter, in which case your claims don’t match my experience. Happy holidays
- ernst_klim 8y ago>If there is an error, it doesn’t matter what the value is. Who said that? In Go the type system let me return both error and value, and there is no static guarantee that value is invalid if error is not nil. >Monads are famously difficult to wrap one’s head around. What's exactly difficult about them? If you do understand this code x := proc1 (arg); y := proc2 (x); return y; what's confusing about this do x <- proc1 (arg) y <- proc2 (x) return y ? Monad is nothing but a pattern (pretty simple in comparison to the majority of GOF patterns, it has only one function for binding two computations of given semantics together) for describing an operational semantics which is just baked in in many imperative languages and could be described by a programmer according to his or her needs with monads (i.e. you don't need to bake don-deterministic, async or parallel computations in a language, you could describe it's semantics with a monad and use it freely). Its closest counterpart is the iterator pattern, which is a generalization of the iteration semantics. It really sounds crazy that it's hard for a programmer to wrap his or her head around operational semantics.
- weberc2 8y ago> there is no static guarantee that value is invalid if error is not nil There doesn’t need to be. An error was returned computing the return value, so it doesn’t make sense to use the value. And if there is something useful to be returned, then you’re dealing with a product type anyway. RE monads, I don’t know why they’re hard to understand, but they are notorious. Probably because they’re more abstract than is helpful in the majority of programs, and they’re often just used to play code golf, removing bits of boilerplate that were never really harmful to begin with (like error handling). I actually like monads and excessive abstraction in general, but that’s because I appreciate a sort of mathematical elegance beyond what is helpful for shipping a product. When I program to feel clever, I use languages like Haskell. When I want to get things done, I use languages like Go. It doesn’t mean Go is better than Haskell or that monads are bad in some absolute sense; only that in general they cost more than they contribute when the objective is shipping a product. Similarly, Go is totally lacking when I want to build something very abstract.
- atombender 8y agoIt's also suprisingly easy to mess up error returning. I've accidentally done things like: a, err := run() if err != nil { b, e2 := cleanup() if e2 != nil { return err // Whoops } return e2 // Double whoops } Since you generally test the happy path more diligently, subtleties like these tend to show up later than desirable (compile time would be the most desirable, of course). The := semantics is partly to blame here, as well as Go's lack of laziness around unused values. Go is curiously strict about unused variables, which never hurt anyone, as opposed to unused values, which absolutely can hurt. For example: a, err := foo() // This err is never used b, err := bar() This is much worse! And the compiler doesn't complain. Fortunately there's a linter called ineffassign that tracks ineffective assignments, and it's part of gometalinter and golangci-lint. But not "go vet". And there's no linter that can detect the first example that I gave. Shadowing is a pet peeve of mine. Go is one of the strictest, most opinionated languages in mainstream use, but it's amazingly lax and unopinionated in certain areas. "go vet" can check for shadowing, but doesn't do so by default, and of course it doesn't test the subtype of shadowing that := causes. Shadowing is usually a bad idea, but Go encourages it through its := assignment semantics.
- sickcodebruh 8y agoGo’s lax attitude about shadowing jumped right out at me, too. Coming to Go recently from TypeScript, where the combination of the compiler and tslint will aggressively bark at me, was jarring. Coukd you point me towards any crash courses on using the Go linters you mentioned to increase safety of my code? I’ll look them up, too, but if you have any resources handy or think there’s work for me beyond RTFM, I’d appreciate it.
- atombender 8y agoFirst, I recommend golangci-lint [1], just because it's much more performant (and designed to be from the start) than gometalinter. It uses much of the same code. Some of the more useful linters, like deadcode, can be a little too slow to run in an editor like VSCode. We run the full linter list as part of our CI tests, and then in development we run it with "--fast", which runs only the fastest one. I've disabled megacheck, which didn't work with Go modules the last time I tried it. Among the prominent ones I use, all enabled by fast mode, are ineffassign (as mentioned), deadcode (detects unused code), govet (runs "go vet"), errcheck (checking for calls where you ignore the error result), structcheck (finds unused struct fields), varcheck (finds unused variables and constants), typecheck (this does the same thing as "go build", basically), and unconvert (detects unnecessary type conversions). The slower ones that are worth turning on are staticcheck (performs lots of static checks) and unused (checks for unused variables etc., but is slower than varcheck and structcheck, apparently). [1] https://www.github.com/golangci/golangci-lint https://www.github.com/golangci/golangci-lint