LWN.net Logo

Google's new "Go" language

Google's new "Go" language

Posted Nov 12, 2009 6:42 UTC (Thu) by nevyn (subscriber, #33129)
In reply to: Google's new "Go" language by epa
Parent article: Google's new "Go" language

As another poster pointed out, having to check return values manually is incredibly losing compared to exceptions.

I disagree. 99% of the time exceptions occur for things that aren't "exceptional", so you have to work around it by catching them everywhere (or create helper functions which catch and return error values). They are also completely invisible exit points from functions, where you have no control over the state of the program ... so if you catch them at higher levels you can't do much more than print a nicer traceback.

With a bit of imagination they could have done something to satisfy both the pro- and anti- exception camps.

I don't see how you can think your success() example is better than their "multiple return values, one of which is an error indicator". But even if you do, you can easily convert from their style to win3^W yours.

A good thing to think about is probably the problem of "open". Pro-exception people tend to want this to throw FileNotFound and PermissionDenied ... because a majority of the time you are reading files that are there and readable.

Anti-exception people tend to hate exceptions here because: 1) Not being "a majority" isn't even close to "exceptional". 2) Putting an exception here makes it ugly as hell to do "open if you can" type operations. 3) #2 often means the resulting code is worse, generally (Eg. python is so bad it's common practice to just use os.path.exists and have a race condition).

Then you have the general complaints for things like at least 99% of the time it's just not that hard to check the return value of open() and dtrt. and anyone with a little experience does it. And that having exceptions on open means that if you add an open call to existing code you need to make sure it can handle the file exception being thrown there, or more likely you have to catch it anyway and do something else (thus. you just have more lines of code to always check the return value anyway).


(Log in to post comments)

Google's new "Go" language

Posted Nov 12, 2009 12:44 UTC (Thu) by epa (subscriber, #39769) [Link]

99% of the time exceptions occur for things that aren't "exceptional", so you have to work around it by catching them everywhere
But equally, if you have 'return 0' to indicate failure, then 99% of the time 'return 0' occurs for things that aren't exceptional, so you have to work around it by testing return values everywhere.
They are also completely invisible exit points from functions, where you have no control over the state of the program
If you choose to let them propagate, yes. This is the biggest problem with exceptions in my view. I would prefer some model where you had to explicitly choose whether to catch and handle the exception or send it up to your caller.
Then you have the general complaints for things like at least 99% of the time it's just not that hard to check the return value of open() and dtrt. and anyone with a little experience does it.
Hmm, and anyone with a little experience also remembers to check the success of every puts() and write() and printf() - not to mention memory allocation?

(Come to think of it, with growable types such as the built-in 'map' but without exceptions, how does Go deal robustly with out-of-memory errors?)

Google's new "Go" language

Posted Nov 12, 2009 13:46 UTC (Thu) by salimma (subscriber, #34460) [Link]

That is the Java checked-exception model -- the only problem with it is that programmers *are* explicitly choosing to let exceptions propagate.

Still better than .NET, where Heljsberg just basically gave up on the idea (ah, the joy of debugging C# applications where *any* exception could happen anytime).

Perhaps the compiler should throw a warning for any re-thrown exception, judging it to be bad style...

Google's new "Go" language

Posted Nov 12, 2009 15:38 UTC (Thu) by nevyn (subscriber, #33129) [Link]

But equally, if you have 'return 0' to indicate failure, then 99% of the time 'return 0' occurs for things that aren't exceptional, so you have to work around it by testing return values everywhere.

I don't think I understand your point. Yes, you get the "error return value" for things that happen often enough they aren't exceptional ... and so you have to test for them. But my point was that exceptions are only good when you don't have to test for them, when you do have to test for them return value testing takes less code and is much easier to read.

Hmm, and anyone with a little experience also remembers to check the success of every puts() and write() and printf() - not to mention memory allocation?

It's common to just test ferror/fclose instead of testing every call to a write function (esp. as close might be the first time you get an error).

(Come to think of it, with growable types such as the built-in 'map' but without exceptions, how does Go deal robustly with out-of-memory errors?)

fgrep "out of memory" **/*.c has quite a few hits. But to be fair, I've never seen a program that has exception based OOM "deal robustly" with it (It often sucks even more because a huge number of lines of code can throw it in those languages) ... admittedly with malloc() it's kind of a slog to do it, and most people prefer to define their own xmalloc() and ignore the problem.

Google's new "Go" language

Posted Nov 12, 2009 23:09 UTC (Thu) by epa (subscriber, #39769) [Link]

I think there are two separate issues: whether to use exceptions at all, and the boundary of what is considered 'exceptional'. You could mechanically convert every 'return 0' style function into an exception-throwing function, and vice versa, and it wouldn't change the amount of work the caller has to do to check the result (especially if some simple 'success(...)' primitive were provided in the language). The only thing that changes is that if the caller neglects to check at all, then in the 'return 0' case the code continues merrily on its way, while with exceptions it gives some kind of error, perhaps an ugly and unexpected one, but nonetheless something.

There is also the question of whether 'failure to open a file is hardly exceptional'. You might imagine an open() function defined as 'returns filehandle, or null if file not found'. Throwing an exception on file not found might be considered too bossy and awkward. But then, I would be content for such a routine to throw an exception if it got a hard I/O error or the current directory no longer existed or some odd condition like that.

But my point was that exceptions are only good when you don't have to test for them, when you do have to test for them return value testing takes less code and is much easier to read.
Not entirely, because often you're content to use the same exception-handling code for a block of several function calls. For example
try {
   f();
   g();
   h();
}
catch (Exception ex) { ... }
is less code than
if (! f()) { ... }
if (! g()} { ... }
if (! h()} { ... }
and less baroque than something like
if (! (f() && g() && h())) { ... }
Still, style discussions could go on all night. I would just like to suggest that some easy syntactic sugar such as 'success()' - or whatever - to unify the try-catch style and the check-return-value style might help reduce boilerplate code; and that exceptions don't have to be a baroque class hierarchy as in Java, but could be something more lightweight that helps the programmer write robust code by default without getting in the way.

Google's new "Go" language

Posted Nov 13, 2009 11:12 UTC (Fri) by ibukanov (subscriber, #3942) [Link]

> try {
> f();
> g();
> h();
> }
> catch (Exception ex) { ... }
>
> is less code than
>
> if (! f()) { ... }
> if (! g()} { ... }
> if (! h()} { ... }

This is a matter of style. Consider an alternative:

f(ok);
g(ok);
h(ok);
if (!ok) { ... }

Granted, this style requires that each function will have an extra ok parameter and the code inevitably will have quite a few ok checks. But when done consistently this style removes most of the ugliness of the repeated if checks and really raises the question about the need of exceptions.

Google's new "Go" language

Posted Nov 13, 2009 11:25 UTC (Fri) by farnz (guest, #17727) [Link]

But now, you've made the contents of f(), g() and h() uglier. Every function has to look something like:

function( ok, ... )
{
  if( !ok )
    return;
  manipulate ...
}

Otherwise, what stops you getting into a state where f() was supposed to set up the world ready for h(), g() was supposed to modify that set up slightly (but fails in interesting and unfriendly ways, because the state that should have been created by f() doesn't exist), and h() bombs badly because the state it expected didn't exist at all, due to f() failing?

Further, every function needs a valid return value for the early exit case. This all just feels to me like a bad reinvention of exceptions, and I don't see how it's any better.

Google's new "Go" language

Posted Nov 13, 2009 13:04 UTC (Fri) by ibukanov (subscriber, #3942) [Link]

> you've made the contents of f(), g() and h() uglier

Yes, but if the function has more then one call site (very typical), the ugliness is limited to the function itself, not to all its callers.

> Further, every function needs a valid return value for the early exit
case.

The most difficult case is when returning an allocated memory/object. If one just returns a null, the following crashes:

a = f(ok);
g(a->somefield, ok);

To deal with such cases one can return a special statically allocated object.

With such tricks the overall impression from a code written 15 years ago in that style was a surprisingly clean and easy to reason about code with ugliness limited mostly to parts that interacts with system API.

Google's new "Go" language

Posted Nov 13, 2009 13:32 UTC (Fri) by farnz (guest, #17727) [Link]

So basically, I get all the pain of return codes (having to mess up all my computation code with error handling logic to pass ok around and deal with it correctly when something goes wrong), and I get none of the benefits of exceptions (being able to write code that ignores errors when I cannot think of a sensible way to handle them)?

Doesn't sound like a good tradeoff to me; the big benefit of exceptions is that it's easy to let them propogate upwards when I can do nothing about the error (the common case). The only code that ends up looking ugly is code which knows how to cope with errors, not code that can't do anything sensible.

In some cases (such as tiny tools), the only sensible thing to do is use an outer wrapper that reports the error (usually language provided, as in Python's tracebacks). In others (such as the application I work on), there are certain errors which I can catch and do something sensible about (e.g. absence of required files triggers me to create default versions). I really can't see how having to add huge piles of ugly error state passing code all over the application would make it neater.

Google's new "Go" language

Posted Nov 13, 2009 13:29 UTC (Fri) by hppnq (subscriber, #14462) [Link]

There is one simple rule that applies here, I guess: refactor your code.

Google's new "Go" language

Posted Nov 13, 2009 14:18 UTC (Fri) by MKesper (guest, #38539) [Link]

(Eg. python is so bad it's common practice to just use os.path.exists and have a race condition).

This is only if you're doing it wrong. Just try to write your file and if it fails, you get your exception. No problem here. :)

Exception handling

Posted Nov 15, 2009 13:48 UTC (Sun) by fjalvingh (guest, #4803) [Link]

A lot of the people here do not really understand exceptions and how they should be used. Statements like
99% of the time exceptions occur for things that aren't "exceptional", so you have to work around it by catching them everywhere (or create helper functions which catch and return error values).
are an indication that exceptions are used in the same way as old return-value based error handling - and that is of course clearly useless. Using it like that indeed does not pose any enhancement.

Sadly enough, the Java checked exception blunder causes a lot of people to hate exceptions and makes it harder to see the real value. Every time you have to catch an exception and rethrow some other one, simply because some idiot decided you could only throw MyBeautifulException is a complete waste of time, and code like that indeed looks like old-style code: you still have to do something for *every* error, and exceptions do not gain you anything. If you see code like that a lot it seems like that is proper use of exceptions - but it isn't.

99% of the time exceptions occur for things that aren't "exceptional"

Exceptions should indeed represent something exceptional, and at least in Java they mostly do. Why is not being able to open a file (possibly for a myriad of different reasons!) not exceptional? Clearly once you decided to open it you intended to do something with it - so not having it is a problem. The real gain in proper exception usage is in better behavioural control AND in WAY better control of error detail: What would *handling* that case mean usually? Reporting an error message? Where? Should the program stop? Is this a GUI application or a console one? What was the *reason* for the open failure? Network error? Bad disk? Disk full? If your subroutine opens 15 files- which one failed? How do you put that in an int??

Having exceptions means you have a meaningful and generic method of passing lots of information on the error, and delegating it's handling means low-level code does not need to handle things it does not know how to handle.

Comparing this with the return value approach is laughable. I still remember the beautiful Microsoft hex error codes used in COM. If you ever got one your only resort was to start the debugger and try to find out what actually went wrong, this could take hours. A single errno code or whatever holds hardly any information, is only correct if the software builder has thought about it deep and hard, and makes for fragile and badly maintainable software.

Other people make the argument that testing for errors in return values is so easy, and that any competent person does that routinely. Well, I probably worked with a lot of incompetents then, and 99% of C software I've seen is probably written by morons also? Because an enormous amount of code does NOT properly check these kinds of errors. Malloc and friends are notorious of course, but you see it everywhere. Clearly it is NOT very easy to check everything for errors, and you have to have a lot of discipline doing it. In addition, if you DO check it is often hard to decide what do do with it. Of course you return "I failed" - but how do you tell the reason?

In reality people are lazy mostly, and there are a lot of mediocre (say normal) programmers out there that often forget checks. I by far prefer that stacktrace, indicating the details and the location of the missing check, above error c0050001.

Proper code using exceptions hardly ever catches them. In properly written code you see only try/finally blocks used for resource management, and only on library boundaries or application main points do you see catches.

They are also completely invisible exit points from functions, where you have no control over the state of the program ... so if you catch them at higher levels you can't do much more than print a nicer traceback.

Another indication that you're simply using them wrongly. Granted, using exception handling means that you *have* to know that exceptions do occur. It means you have to do proper resource handling. It does not mean that you have to catch everything. And you have full control over the state of the program, of course; writing finally or catch is not much different from if(returnvalue==) - you have to know which functions to check there also.

Anti-exception people tend to hate exceptions here because: 1) Not being "a majority" isn't even close to "exceptional". 2) Putting an exception here makes it ugly as hell to do "open if you can" type operations. 3) #2 often means the resulting code is worse, generally (Eg. python is so bad it's common practice to just use os.path.exists and have a race condition).

This statement borders on the silly side. I way prefer "ugly" code above code that fails. It is not even ugly if you make that few-line helper function "try { return new FileInputStream(f); } catch(Exception x) { return null } method for this exceptional case. Point 3 is valid though - people too lazy todo things proper make lousy code. The point then would be to make the number of cases where they would do that as small as possible.

Finally, code using proper exception handling is smaller and easier to read, and if done properly it is easier to write correct code which provides meaningful (and correct) errors when it fails. It also encapsulates a quite hard subject (error handling) in a generic way.

If you write code doing lots of SQL for instance having every statement result checked manually is horror; you have to write some framework (with it's own conception of handling errors) just for that, and even then you have to check after every statement. Useless code.

Exception handling

Posted Dec 8, 2009 7:02 UTC (Tue) by yvesjmt (subscriber, #38201) [Link]

You are assuming that return values are always simple error codes.

In Go, errors usually carry a descriptive string.

http://golang.org/doc/effective_go.html#errors

Copyright © 2012, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds