12 ms·
I am so tired of reading Java/C++/Python code that just slaps try/catch around several lines. To some it might seem annoying to actually think about errors and
by AnonymousPlanet 2y ago
I am so tired of reading Java/C++/Python code that just slaps try/catch around several lines. To some it might seem annoying to actually think about errors and error handling line by line, but for whoever tries to debug or refactor it's a godsend. Where I work, try/catch for more than one call that can throw an exception or including arbitrary lines that don't throw the caught exception, is a code smell.
So when I looked at Go for the first time, the error handling was one of the many positive features.
Is there any good reason for wanting try/catch other than being lazy?
- slt2021 2y ago>the error handling was one of the many positive features. sounds good on paper, but seeing "if err!=nil" repeated million times in golang codebases does not create positive impression at all
- slt2021 2y agogolang keyboard ftw https://pbs.twimg.com/media/DCIF7-2W0AEAv9c.jpg https://pbs.twimg.com/media/DCIF7-2W0AEAv9c.jpg
- Mawr 2y agoYes but the impression is largely superficial. The error handling gets the job done well enough, if crudely.
- FuckButtons 2y agoThe ability to quickly parse, understand and reason about code is not superficial, it is essential to the job. And that is essentially what those verbose blocks of text get in the way of.
- Mawr 2y agoSure, I just don't think it's that significant. Humans don't read/parse code character-by character, we do it by recognizing visual patterns. Blocks of `if err != nil { }` are easy to skip over when reading if needed.
- bbkane 2y agoI agree, though I was really surprised to learn this when reading Go code. Much easier to skip over than I was expecting it to be
- IshKebab 2y agoThe idea that error handling is "not part of the code" is silly though. My impression of people that hate Go's explicit error handling is that they don't want to deal with errors properly at all. "Just catch exceptions in main and print a stack trace, it's fine." Rust's error handling is clearly better than Go's, but Go's is better than exceptions and the complaints about verbosity are largely complaints about having to actually consider errors.
- Capricorn2481 2y ago> The idea that error handling is "not part of the code" is silly though. My impression of people that hate Go's explicit error handling is that they don't want to deal with errors properly at all. "Just catch exceptions in main and print a stack trace, it's fine." I'm honestly asking as someone neutral in this, what is the difference? What is the difference between building out a stack trace yourself by handling errors manually, and just using exceptions? I have not seen anyone provide a practical reason that you get any more information from Golangs error handling than you do from an exception. It seems like exceptions provide the best of both worlds, where you can be as specific or as general as you want, whereas Golang forces you to be specific every time. I don't see the point of being forced to deal with an "invalid sql" error. I want the route to error out in that case because it shouldn't even make it to prod. Then I fix the SQL and will never have that error in that route again.
- IshKebab 2y agoThe biggest difference is that you can see where errors can happen and are forced to consider them. For example imagine you are writing a GUI app with an integer input field. With exception style code the overwhelming temptation will be to call `string_to_int()` and forget that it might throw an exception. Cut to your app crashing when someone types an invalid number. Now, you can handle errors like this properly with exceptions, and checked exceptions are used sometimes. But generally it's extremely tedious and verbose (even more than in Go!) and people don't bother. There's also the fact that stack traces are not proper error messages. Ordinary users don't understand them. I don't want to have to debug your code when something goes wrong. People generally disabled them entirely on web services (Go's main target) due to security fears.
- marcus_holmes 2y agoAs an experienced Go dev, this is literally not a problem. Golang code has a rhythm: you do the thing, you check the error, you do the thing, you check the error. After a while it becomes automatic and easy to read, like any other syntax/formatting. You notice if the error isn't checked. Yes, at first it's jarring. But to be honest, the jarring thing is because Go code checks the error every time it does something, not because of the actual "if err != nil" syntax.
- LinXitoW 2y agoJust because you can adapt to verbosity does not make it a good idea. I've gotten used to Javas getter/setter spam, does that make it a good idea? Moreover, don't you think that something like Rusts ? operator wouldn't be a perfect solution for handling the MOST common type of error handling, aka not handling it, just returning it up the stack? val, err := doAThing() if err != nil { return nil, err } VERSUS val := doAThing()?
- foldr 2y agoI personally have mixed feelings about this. I think a shortcut would be nice, but I also think that having a shortcut nudges people towards using short-circuit error handling logic simply because it is quicker to write, rather than really thinking case-by-case about what should happen when an error is returned. In production code it’s often more appropriate to log and then continue, or accumulate a list of errors, or… Go doesn’t syntactically privilege any of these error handling strategies, which I think is a good thing.
- gwd 2y agoThis. Golang's error handling forces you to think about what to do if there's an error Every Single Time. Sometimes `return err` is the right thing to do; but the fact that "return err" is just as "cluttered" as doing something else means there's no real reason to favor `return err` instead of something slightly more useful (such as wrapping the err; e.g., `return fmt.Errorf("Attempting to fob trondle %v: %w", trondle.id, err)`). I'd be very surprised if, in Rust codebases, there's not an implicit bias against wrapping and towards using `?`, just to help keep things "clean"; which has implications not only for debugging, but also for situations where doing something more is required for correctness.
- sethammons 2y agoI find that knowing where my errors may come from and that they are handled is essential to my job and missing all that info because it is potentially in a different file altogether gets in the way
- lelanthran 2y ago> sounds good on paper, but seeing "if err!=nil" repeated million times in golang codebases does not create positive impression at all Okay, but other than exceptions, whats the alternative?
- nindalf 2y agoThe ? Operator in Rust?
- dureuill 2y agoMore than just that, Result in general also prevents from accessing the value when there is an error and accessing an error when there is a value.
- throw-the-towel 2y agoThe absence of that safeguard in Go is a feature. It's used when the error isn't that critical and the program can merrily continue with the default value. Of course, this is also scarily non-explicit.
- lelanthran 2y agoGood point. I only briefly tried Rust and was turned off by the poor ergonomics; I don't think (i.e. open to correction) that the Rust way (using '?') is a 1:1 replacement for the use-cases covered by Go error management or exceptions. Sometimes (like in the code I wrote about 60m ago), you want both the result as well as the error, like "Here's the list of files you recursively searched for, plus the last error that occurred". Depending on the error, the caller may decide to use the returned value (or not). Other times you want an easy way to ignore the error, because a nil result gets checked anyway two lines down: Even when an error occurs, I don't necessarily want to stop or return immediately. It's annoying to the user to have 30 errors in their input, and only find out about #2 after #1 is fixed, and #3 after #2 is fixed ... and number #30 after #29 is fixed. Go allows these two very useful use-cases for errors. I agree it's not perfect, but with code-folding on by default, I literally don't even see the `if err != nil` blocks. Somewhat related: In my current toy language[1], I'm playing around with the idea of "NULL-safety" meaning "Results in a runtime-warning and a no-op", not "Results in a panic" and not "cannot be represented at all in a program"[2]. This lets a function record multiple errors at runtime before returning a stack of errors, rather than stack-tracing, segfaulting or returning on the first error. [1] Everyone is designing their own best language, right? :-) I've been at this now since 2016 for my current toy language. [2] I consider this to be pointless: every type needs to indicate lack of a value, because in the real world, the lack of a value is a common, regular and expected occurrence[3]. Using an empty value to indicate the lack of a value is almost certainly going to result in an error down the line. [3] Which is where there are so many common ways of handling lack of a value: For PODs, it's quite popular to pick a sentinel value, such as `(size_t)-1`, to indicate this. For composite objects, a common practice is for the programmer to check one or two fields within the object to determine if it is a valid object or not. For references NULL/null/nil/etc is used. I don't like any of those options.
- dalyons 2y agoThe huge volume of boilerplate makes the code harder to read, and annoying to write. I like go, and I don’t want exceptions persay, but I would love something that cuts out all the repetitive noise.
- jacobr1 2y agoThe example in the article is a good one. Result and Optional as first class sum types
- RedComet 2y agoThat just changes the boilerplate from if's to match's.
- Mawr 2y agoSee the example with the `?` operator: https://github.com/borgo-lang/borgo?tab=readme-ov-file#error-handling-with--operator https://github.com/borgo-lang/borgo?tab=readme-ov-file#error... The main benefits of a Result type are brevity and the inability to accidentally not handle an error.
- RedComet 2y agoYes, but that isn't necessarily a feature of option types. Is it the case that similar sugar for the tiresome Go pattern couldn't achieve similar benefits?
- Mawr 2y agoPerhaps, but there have been several proposals along those lines and nobody seems capable of figuring out a sensible implementation. A funny drawback of the current Go design that a Result type would solve is the need to return zero values of all the declared function return types along with the error: https://github.com/golang/go/issues/21182 https://github.com/golang/go/issues/21182.
- 2y ago
- dgellow 2y ago> Is there any good reason for wanting try/catch other than being lazy? In a hot path it’s often beneficial to not have lots of branches for error handling. Exceptions make it cheap on success (yeah, no branches!) and pretty expensive on failure (stack unwinding). It is context specific but I think that can be seen as a good reason to have try catch. Now of course in practice people throw exceptions all the time. But in a tight, well controlled environment I can see them as being useful.
- tredre3 2y ago> In a hot path it’s often beneficial to not have lots of branches for error handling. This is true but the branch isn't taken unless there's an error in Go. Given that the Go compiler emits the equivalent of `if (__unlikely(err != nil)) {...}` and that any modern CPUs are decently good at branch prediction (especially in a hot path that repeats), I find it hard to believe that the cost would be greater than exceptions.
- tadfisher 2y agoYes, it's the ability to unwind the stack to an exception handler without having to propagate errors manually. Go programs end up doing the exact same thing as "try/catch around multiple lines" with functions that can return an error from any point, and every caller blindly propagating the error up the stack. The practice is so common that it's like semicolons in Java or C, it just becomes noise that you gloss over.
- Mawr 2y agoThe difference is that all code paths are explicitly spelled out and crucially that the programmer had to consider each path at the time of writing the code. The resulting code is much more reliable than what you end up with exceptions.
- bootsmann 2y agoDo you really do that in practice, or do you just blindly go 'if err != nil return nil, err'? Because fundamentally the function you called can return different errors at any point so if you just propagate the error the code paths are in fact not spelled out at all because the function one above in the hierarchy has to deal with all the possible errors two calls down which are not transparent at all.
- throwaway894345 2y agoIn Go, no one really blindly returns nil, err. People very clearly think about errors—if an error may need to be actioned on up the stack, people will either create a named error value (e.g., `ErrInvalidInput = errors.New(“invalid input”)` or a named error type that downstream users can check against. Moreover, even when propagating errors many programmers will attach error context: `return nil, fmt.Errorf(“searching for the flux capacitor `%s`: %w”, fluxCap, err)`. I think there’s room for improvement, but Go error handling (and Rust error handling for that matter) seem to be eminently thoughtful.
- 4star3star 2y ago
- mixmastamyk 2y ago> try/catch for more than one call that can throw an exception or including arbitrary lines You generally need to skip all lines that the exception invalidates. That's why it's a block or conditional.
- Mawr 2y ago> Is there any good reason for wanting try/catch other than being lazy? It's the best strategy for short running programs, or scripts if you will. You just write code without thinking about error handling at all. If anything goes wrong at runtime, the program aborts with a stacktrace, which is exactly you want and you get it for free. For long-running programs you want reliability, which implies the need to think about and explicitly handle each possible error condition, making exceptions a subpar choice.
- tayo42 2y agoYou can print or log the stack trace of the exception in python.
- BillyTheKing 2y agoI agree, I don't really understand everyone's issue with err != nil.. it's explicit, and linters catch uncaught errors. Yes the ? operator in Rust is neat, but you end up with a similar issue of just matching errors throughout your code-base instead of doing err != nil..
- nyssos 2y agoThe problem is that you're forced to have four possible states 1. err != nil, nondefault return value 2. err != nil, default return value 3. err == nil, nondefault return value 4. err == nil, default return value when often what you want to express only has two: either you return an error and there's no meaningful output, or there's output and no error. A type system with tuples but no sum types can only express "and", not "or".
- marcus_holmes 2y agothis is true, but not a problem. Go's pattern of checking the error on every return means that if an error is returned, that is the return. Allowing routines to return a result as well as an error is occasionally useful.
- throwaway894345 2y agoI mean, I wish Go had sum types, but this really isn’t a problem in practice. Every Go programmer understands from day 0 that you don’t touch the value unless the error is nil or the documentation states otherwise. Sum types would be nice for other things though, and if it gets them eventually it would feel a little silly to continue using product types for error handling (but also it would be silly to have a mix of both :/).
- throwaway894345 2y agoYeah, also you almost always need to annotate errors anyway (e.g., `anyhow!`), so the ? operator doesn’t really seem to be buying you much and it might even tempt people away from attaching the error context.
- Mesopropithecus 2y agoTry-blocks with ~one line are best practice on code based I have worked with. The upside is that you can bubble errors up to the place where you handle them, AND get stack traces for free. As a huge fan of Result<T, E>, I have to admit that that's a possible advantage. But maybe that fits your definition of lazy :).