Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Go's error handling reminds me of C or PHP error handling. Check the status code. Since everything can mess with that error condition, you have to be careful in how you handle it, but there is nothing forcing you to handle it. I like exceptions because handling an error is default delegated until something wants to or can handle the error. In the case of go, if there is a layer above your function that should process the error, you have to do that if/err in each function that isn't going to handle it. I strongly believe in halting on error as the default response, and unwinding until the correct layer can proceed.


>PHP

Oh no, don't even put them in the same sentence. I am no fan of Go, but its error handling approach is beautiful compared to PHP. Every time I call an internal function I have to go through the docs — does it return a NULL, an int, a boolean, or something else? Does 0 signal an error condition, or is it a valid value? Do I have to perform a strict check for NULL/false then? Or is it -1 (see link below)? The situation is generally better with third-party libraries though — they tend to just throw exceptions (if that matches your definition of "better").

https://www.php.net/manual/en/function.openssl-verify.php


Rightly or wrongly, many PHP functions are thin wrappers around third-party C libraries and they tend to return values without interpretation of the results.

For your example of openssl_verify see https://linux.die.net/man/3/x509_verify_cert

It can certainly be confusing but at least it is documented.


I'm curious who it was who first started implementing those wrappers and thought "Yeah, this is good enough."


The late '90s and early '00s were a strange time. It had plenty of shotguns aimed at feet and no one to suggest a better approach. At least no one with enough reach.

Also [1]:

>> "I don't know how to stop it, there was never any intent to write a programming language [...] I have absolutely no idea how to write a programming language, I just kept adding the next logical step on the way"

I assume he's better at it 16 years later.

[1] https://en.wikipedia.org/wiki/PHP#History


PHP is a bit of hyperbole, but otherwise correct. :) Ruby, Python, Javascript all support exceptions. PHP does support exceptions, but it's core libraries do not use them. Gotta trust PHP to do the worst of both worlds approach. Perl 6 has it but not sure where the world is in the 5 -> 6 migration. I'm not going to count eval(...) as exception handling as a language feature in Perl 5. :)


I'm not going to count eval(...) as exception handling as a language feature in Perl 5. :)

You should. It's a perfectly valid exception handling mechanism. It's unfortunate that the name "eval" is overloaded for two separate behaviors:

  * catch exceptions thrown as strings or objects

  * compile and execute code from a string
Other than the name, they're different behaviors.


I am not a Perl expert. Only worked on some simple install tools written it a couple years ago. Re-reading docs, eval(...) traps exceptions (die), but requires manual inspection of error state, instead of automatically halting. It operates more like Go/C/PHP in that regard.


I'm so with you. Exception handling is an awesome tool. I love the way C# offers you different constructs like try/catch/finally, exception pattern matching or usings, which automatically clean up objects.

While writing services I often need to centrally log an error and retry the process later on. Exception handling really reduces the mental load regarding error handling in this case.

On a semantic level, there are forseeable errors you need to check locally (for example wrong user input) or exceptional errors which can happen at any time (for example file access errors or out of memory scenarios). Exceptions are a perfect match for the second type.

I don't really understand since when or why exceptions are uncool. It hurts my eyes looking at all that error checking code in other languages.


There's been a bit of a backlash against exceptions in new languages (and attempts to "ban" them in old) and I actually kind of get it, especially having been a C++ developer. You'll see stuff like the Google C++ standard saying "don't use exceptions" but then if you want to apply RAII (and you really do), you have a constructor and the only way to indicate an error is via exceptions.

Also, how about this wonderful syntax:

    class OrangeSite
    {
        Someobject m_object;
    public:
        OrangeSite(Someobject o) : m_object(o) {
            try {
                // construct object
             } catch (std::exception& e) {
                // exceptions for anything in the body above
             }
        } catch (std::exception& e) {
            // exception handler for anything that failed in the initializer lists
        }
    };
I mean, kill it with fire. That is the only way to catch exceptions that occur in initializer lists and yes you have multiple catches. C# et al haven't reinvented all the same mistakes, but still, there's still two ways to control flow: return values and exceptions.

What is really needed is the ability to do the same sort of filtering on error types. You could actually do it in golang with a type assertion, but it would require implementing a type that implements the error interface and type assertions. It's ugly.

Regarding your errors/exceptions, I've heard this before and I agree with it. There are error conditions, from which the program can and is expected to recover, and exception conditions, from which the program cannot recover. Out of memory is a perfect exception condition.

However, this semantic distinction is entirely lost - a lot of error conditions are reported as exceptions. In my above example you have absolutely no choice in the matter; having no error type, languages like Java/C# etc heavily encourage, even if they don't mean to, return by exception.

The golang and rust mapping for these concepts is panic and... panic! respectively. You can catch and recover from panics if need be in golang; in Rust panic handling is bounded by thread and if a panic occurs on the main thread, the runtime triggers an exit. As far as I'm aware, the only way to recover from a panic is like this:

    fn unrecoverable_error() -> thread::Result<()> {
        return panic::catch_unwind(|| {
            panicking_function()
        });
    }
with panic="unwind" set in cargo.toml. If the thread::Result seems bizarre. it's because panic::catch_unwind basically spins up a thread to isolate that panic.

Thus panics in Rust really are for things that should absolutely crash the program there and then. Everything else is a result type and you can shove any type you like into the error field. Golang is a bit more forgiving in that panics can be caught, but the language still encourages the use of errors rather than panics.

At the end of the day, at some point there's going to need to be a translation between some of the internal errors and what the end user sees and this process is going to end up in some boilerplate, be it lots of classes implementing some exception superclass, type or interface, or a bunch of if statements to handle exceptions. I don't think this is avoidable and one line magic code makes me uneasy - I have some experience now managing developers who like magic and want to avoid, at all, costs, translating errors to have friendly user output.

Lastly, there are workaround to this sort of boilerplate. Here's a simplified snippet I use to write this stuff whenever I write go:

    snippet errif "Call function then check error" w
    ${1:var}, err ${2::}= ${3:func}(${4:args})
    if err != nil {
        fmt.Println("$5 %v", err)
        return err
    }
    $0
    endsnippet
It's 2019, so, most editors support this kind of thing. I'm using vim, but VS Code supports it too if you feel the need for a whole browser rendering engine just to edit some text ;) the placeholders allow me to fill in the template. Every time I find myself writing boilerpolate code, I write one of these and automate the task as much as possible. vim-go has a bunch of them: https://github.com/fatih/vim-go/blob/master/gosnippets/UltiS... and I'm not sure how I've programmed all these years without this kind of thing. I have snippets for everything.


Yeah C++ function try blocks are really ugly. I guess just too many people used and misused exceptions in C++ or Java. Moreover there was a time where exceptions had a bad reputation because of performance.

Working with different kinds of people, beginners and seniors, I learned that everyone has their own way of thinking about errors. It's hard or even impossible to get a consistent code style. Code formatters like go-fmt surely help. But do you trust everone to handle each error case appropriately?

What I like about exceptions is, that our software handles each and every possible error now and in the future consistently in a fail-fast way with an accurate stacktrace for free. I can sleep well, because I can be sure our main loop won't crash. Everyone can write plugins without me moving a finger. It frees us to think about other things first and errors second.


Well, as a programmer I like exceptions too as error handling becomes a breeze.

But as a user I hate them and the reason a simple: Because the developer doesn't have to handle every case, many developers just don't know what types of errors/exceptions can happen at runtime and unless they catch all Exceptions at the top-level, their programs will crash every once in a while.

Yes, it is a bit unfair to judge a feature like exceptions by the resulting code quality, as you certainly can use them responsibly, but my personal impression is that exceptions mislead developers to writes code of lower quality (myself included). That is why I like the Go approach to (some extent).


The problem is that even if the programmer is forced to deal with every error condition, they might think a particular error is not likely and write a dummy handler (like print something and hope for the best). In that case exceptions are superior because at least they wind down the stack and allow a caller to deal with the situation, and prevent a crash.


When people don't care, you have to make doing it right easier than doing it wrong.


Or at least force it to be an intentional decision to do it wrong, which go does. Exceptions permit the programmer to be unaware that the function they're calling could throw an exception.


However, if you don't have/use exceptions and you forget to handle an error code, then instead of a loud exception, the situation is silently ignored and shows up as some problem elsewhere. For instance, oops, the program believed it saved something to a file, but that wasn't actually done; it didn't notice the failed write.


I dislike exceptions for the same reason, because it’s often not clear if the error will come through an indirect channel (the exception), or in the return value. It’s better in languages that express that in their type system.

And unless you catch the common ancestor for all exceptions, it’s difficult to know what you need to look out for. This is especially true in dynamic languages like Ruby.

To my mind, exceptions enter a language when the language lacks the semantics to expose them in the return value, where true/false/null/errno is too primitive for larger systems.

Option, Result, and Either are great alternatives that allow you to propagate problems to deal with them at a higher level, with explicit acknowledgment that an error might occur.

Exceptions can be ignored and caught in random places with little to no context, so long as they’re further up the stack. Or you just get stuff like:

    try {
      Application->runTheWholeThing();
    } catch (e) {
      print “something went wrong”;
    }


I've been avoiding the discussion around Dynamic/Static s/types/languages/g , documentation/interface definition, and typed exceptions. If I go off on that rant, I'll be here all day.

If you are catching error in random places, you are doing it wrong. As you pointed out, context is critical. My point is generally, the location of code that generated the error may not have sufficient context. If you have enough context locally (or through further inspection) on how to proceed, then you handle it at that level. If you don't have enough context, then you send it up the call chain.

In your code example above, if after that catch, the program exits, then I argue it is correct. Some exception that was unrecoverable happened somewhere in the application, thus it needs to halt. If the error is recoverable and the application should continue, the exception should be handled somewhere else in the application.


There's one problem with traditional exception model. Basically there's hidden untyped program flow behind the scenes. So it's like running wild JavaScript behind your strongly typed program, because you can never be sure what exceptions could be thrown from that function. Java tried to tackle this problem, introducing at least optional typing with its checked exceptions but failed miserably. That said, I don't think that exceptions are bad idea, they just have to be improved. Error handling via explicitly returning error is a step back.


> I strongly believe in halting on error as the default response, and unwinding until the correct layer can proceed.

Isn't this is exactly what happens in Go code with the current implementation? At least all the Go code I've read seems to do this.

If you don't know what to do or can't do anything, you return the same error, preferably adding more details to the error string. If you do know what to do you handle it.

I much prefer that everyone has to make an explicit choice on what to do; I find following code with exceptions much more painful.


The Go default (if you don't do anything with the error value) is to just keep trucking as if nothing happened.

The community convention seems to be keep rewriting the `if err` dance, but there's nothing encouraging that in the language itself.


This is where I wish more languages would learn from Erlang. Everything you said applies, but the correct layer is defined upfront based solely on what I'm trying to do, not a complex combination of what kind of error happened, what kind of error I expected, and what I'm trying to do.


Well, there is one thing forcing you to handle errors, you have to deliberately suppress them or the compiler yells at you.

https://play.golang.org/p/3ujNLwOdXZf

But explicitly having to handle them versus implicit exception based handling is a tradeoff. Often it isn't clear which exceptions a function might throw unless you've read all the documentation or you're the one who wrote it. When I'm coding my own stuff I'd tend to prefer exceptions but in larger teams I'd tend to prefer explicit handling.

And then there are languages, e.g. Haskell and its Maybe, that manage to combine the best of both worlds.


You just replace err with _ and it doesn't care anymore:

https://play.golang.org/p/in5pgl11fO7


Is there a way in Haskell to have Maybe with an optional callback?


You mean a CarlyRaeJepsen monad? A "Call Me Maybe?"


My somewhat puckish opinion is that go and php are siblings and have similar social patterns.

Easy to deploy, easy to write, tons of docs on the official site, massive deployment bases, and also genuinely holding back progress in reliable software writing; both cover up the irreducible complexity in professional software development that other, fussier tools, force you to handle.


Especially true as API design seems to have moved away from using exceptions in non-exceptional situations; handling them locally is not desirable in most cases. In the old days we'd have a Parse method that would blow up the program if your string had characters other than digits; now we have a TryParse that returns e.g. an option type. It could also be because of the industry I'm in, but I've written (and read) extremely little exception-handling code in the last few years, because a real exception is not something my little domain method can do anything about.

That said, exception handling syntax is ugly and cumbersome in most languages I've seen. Whether it's try/catch/finally with braces or begin/rescue/ensure/end or whatever. It's also rarely written in a way that tells the reader where exactly the exceptions come from, it's just a blanket for a large block of code.

Something that ties the handling directly to the statement that breaks, without the noise of including the 'exceptional' behavior alongside the main logic, might be an improvement:

  result = do_something(x, y, z)
      handle SomeArgumentError with my_nre_handler(x, y, z)
      handle RecordNotFound with missing_record_handler(x)


Isn't the last example similar to a sum type and using it in a match?


I provided the Go community with a long menu of "Requirements to Consider for Go 2 Error Handling" [1].

Sadly, the Go team apparently decided all of it was just too involved, so they picked the minimalist try().

[1] https://gist.github.com/networkimprov/961c9caa2631ad3b95413f...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: