17 ms·
Why I’m not leaving Python for Go
- rachelbythebay 14y agoRejecting something because it's "from the 1970s" seems like a particularly weak way to roll. Automatically equating "old" with "broken" is just as lazy as automatically equating "new" with "pointless". The world is far more nuanced than that.
- sp332 14y ago"Old" as in, the last time this seemed like a good idea was the 70's.
- TazeTSchnitzel 14y agoI'll agree that this is perhaps the single thing that I don't like about Go. Now, you can argue it helps with "explicit error handling" and all that, but in that case you might as well have must-be-caught exceptions. Anyone want to fork Go?
- nilsbunger 14y agoOnly if we call it Goto
- deleted 14y ago[deleted]
- nostrademons 14y agoOr perhaps GoTwo.
- jlgreco 14y agoWhat is it about panic/recover that forking the language, not merely using Go in a non-idiomatic way, would be necessary? I could see forking it to add generics in a way that the official guys aren't satisfied with, but why is that needed to use another style of error handling?
- TazeTSchnitzel 14y agoBecause all of the rest of the ecosystem uses a different way, and switching between two different styles isn't fun.
- jlgreco 14y agoWhat would forking the language do to solve that? What changes would you actually make? Would it just be a matter of giving it a new name so that people didn't expect idiomatic Go things to work with it? Are we talking about just forking its standard library and not actually changing the language?
- luriel 14y agoAnyone using panic/recover across API boundaries to emulate exceptions should have their brains gofmt-d. But so far I have not noticed anyone doing this, perhaps because actually returning and handling errors properly is not as much of an issue as most people make it to be.
- jlgreco 14y agoI personally like Go's error handling so I wouldn't consider doing it, but what exactly would be the issue with doing so? The reasons not to that I have seen are all basically boil down to "Because you're not supposed to, it's not what it's for". Because of how recover works it certainly wouldn't make sense to use it exactly the same as try/catch is generally used, but what exactly would be the problem with using panic/recover for more common cases (less 'exceptional' cases...) if we've already decided to not value writing idiomatic code? It strikes me as a terrible idea, but try/catch does too.. I guess I don't understand why it would be even worse than that.
- luriel 14y agoBecause one of the nice things about Go is that I don't have to worry about this. Is not just unidiomatic code in itself, but it creates unidiomatic APIs that behave in ways Go programmers don't usually have to worry about. Unlike Python programmers, who should always be worrying about what exceptions any function or method they call could throw, hell, even setting a property or adding an item to a map or list can throw all kinds of exceptions. And this is rarely documented, and when it is documented it is usually incomplete, because whoever wrote that code doesn't know what exceptions might be thrown by any other code he calls.
- georgemcbay 14y ago"in that case you might as well have must-be-caught exceptions" Errors aren't the same things as exceptions.
- TazeTSchnitzel 14y agoExceptions are a way of handling error conditions.
- azth 14y agoIf anything, they should have used sum types instead of product types to handle errors.
- dchest 14y agoSo instead of writing x, err := something() if err != nil { handleError(err) } n, err := somethingElse(x) if err != nil { handleError(err) } andSoOn() you could write switch x := something() { case error: handleError(err) case string: switch n := somethingElse(x) { case error: handleError(n) case int: andSoOn() } } ?
- lmm 14y agoYes. The advantage of the second form is that x := something() n := somethingElse(x) andSoOn() no longer compiles. Obviously you would also want some syntactic sugar to make it look nice, but that's quite simple.
- masklinn 14y agoThere's also the option of doing it on an explicitly tagged union, Erlang-style: `something` returns not `Value` or `Either(Error, Value)` but `{ok, Value} | {error, Reason}`. This means you can handle the error: case something() of {ok, Value} -> %%; {error, Reason} -> %% end or you can "ignore" it {ok, Value} = something() but (and this is important) the latter *will not pass silently if `something()` returns an error. Instead, it will raise a "BadMatch" fault, similar to an Haskell-ish let (Right value) = something() where `something :: Either a b`
- bproctor 14y agoI don't understand why anyone would "leave" a language that was working well for them just because of some hype. Don't get me wrong, I think we should all be striving to learn new things, but that doesn't mean we should just drop everything to jump on the next bandwagon.
- dmix 14y agoWhy not? There are early adopters for everything. They are essential to forward progress.
- deleted 14y ago[deleted]
- mquander 14y agoYour statement seems self-contradictory. Should we strive to learn new things, or should we not jump on the next bandwagon? Are you saying that we should try new things but not really try them very hard, or what? There is such a thing as progress, and in my opinion the way you get it is by spending lots of time trying new things and seeing which ones work. So I'm in favor of "leaving a language that was working well for them just because of some hype."
- zobzu 14y agoBecause hype != progress. Hype is basically a bag full of air. You can try new things, but abandon the other ones because of hype (or thinking it's better cuz "ppl who don't know anything about languages said so") instead of true technical merit is pretty fucked up :) Now then again Go ain't bad, but I don't see it being better enough than Python to switch in this case. Python in better for many things as well. Not a large step enough.
- mquander 14y agoHype is a bunch of people claiming something is good. If there are enough people, you should probably go find out whether it really is good. If it really is, then voila! Progress happened. If it's not, you stop. This guy is busy finding out, that's all.
- Jabbles 14y agoIt's not "tempting to ignore errors". You're the programmer, if you want to ignore it, do so. I don't know why you'd want to ignore the returned value and possible error on a GET request - given that 1) it can fail for so many reasons, and 2) the suggestion that you might be "GETTING" something. Ignoring the error of a print statement is more acceptable since it's unlikely to occur and there's rarely a lot you can do about it anyway. Error handling is explicit, like the quote you quoted said it should be. If you ignore the returns (either explicitly or implicitly) you pay the price in debugging. "leaving me confused as to which error happens when" Panics happen when something very bad happens. You shouldn't be accessing off the end of an array, it signals that you've probably programmed it incorrectly, rather than it being a problem with your input (~ish). Same with divide-by-zero and type assertions. However, they provide another control path that in a limited number of situations, can give elegant results.
- d0m 14y agoThat's his whole point. He doesn't want to ignore errors but feels like Go is so verbose about it that it's a pain for programmers to deal with it. Example in case, print. Would you check its return value every time you call it? No. Contrast that to other languages, such as python, where you don't ignore the errors because you know that if something goes wrong, an exception will be raised. It lets you do stuff like: def whatever(request): my_int = int(request.POST['some-param']) Note that, in that case, we don't check if 'some-param' is in POST. Neither what happens if it's not a number.. Basically, an exception will be raised and a 404 will be shown.
- 0xABADC0DA 14y agoIt's actually worse than that. The blog has error handling code like: if err := datastore.Get(c, key, record); err != nil { return &appError{err, "Record not found", 404} } This is pretty typical Google Go error handling, where errors are only actually used as boolean flags. Errors are universally returned as type 'error' so to actually do anything with the error you have to first cast it to some specific type. But that type isn't part of the method signature so you have to dig around in the source or, if you are very lucky, it was documented someplace. You end up having actual error handling plus even more error handling for if you got the error type wrong. For instance if datastore could return 'record not found' or 'io error' then they would usually be treated the same way by the code because to do otherwise would be even that more of a hassle.
- zik 14y agoI suppose it's all a matter of taste. The error handling design decision is one thing I really like about Go. I write high reliability embedded software and "throw/catch" just doesn't cut it for me since it obscures possible error conditions thrown from subroutines. I want to have to explicitly deal with those errors whenever they might occur and error returns are a good way of doing that. For every language feature someone likes there's someone else who hates it, and vice-versa.
- je42 14y agoI cant follow. Throw/Catch is as powerful as a error return value but it does not let you ignore errors without writing any code.
- wonderzombie 14y agoUnless they're unchecked exceptions, of course. In Python, all exceptions are unchecked. The proliferation of checked exceptions all throughout the code is a whole other ball of wax.
- je42 14y agoChecked exception need to be handled properly. It depends on the sanity of the API. However, the standard way how to handle checked exception is a lot easier to maintain than error return values.
- wonderzombie 14y agoEasier to maintain how? I don't think I see it. Return values, at least as a mechanism, are strictly simpler; they do not introduce new control flow. They just use "return." Control always reverts to the caller. For an exception, you don't know whether it's the caller, the caller's caller, some generic exception handler, or what. It's no fun trying to track down all of the callers of A, some subset of which might not catch exception X, then track down the subset of those callers which catch X. Otherwise adding or removing error codes seems equivalent to adding or removing exceptions. Same with trying to decide which exceptions/errors to handle or bubble up.
- cyberroadie 14y agoThe author of the blog post is overlooking a major feature here. Go does multiple return types, so you can do things like this: i, err := getValue() however if you want to ignore a return variable or in this case the returned error you use an underscore like this (same as in haskell): i, _ := getValue() This is Go's mechanism for handling (or ignoring errors) which imho is really nice because you don't have to learn anything extra, like throwing/catching error mechanisms
- je42 14y agoThat's not the same as handling an error in a central spot automatically.
- wonderzombie 14y agoHandling errors in one spot doesn't work for all situations, though, right? Especially in an OO environment an object may need context for retrying a request, or some other kind of error decision making. So in practice some exceptions percolate through the object graph, while others are handled in-house, so to speak.
- je42 14y agoIt works for most situations. If you need retry logic - exceptions is the wrong way to go. Errors returned by the standard library are most likely not errors you want to retry by default.
- wonderzombie 14y agoThat depends, though, doesn't it? Just about anything network-related is subject to flakiness, disconnection, and timeouts. That means HTTP, database connections, sockets, etc.
- noah256 14y agoPsh, obviously trigger_doomsday_device will only respond to an http POST. Once again the president has been saved, thanks to HTTP Pedantry.
- politician 14y ago> HTTP Pedantry I propose a new HTTP verb, MEH, to address the belief that the HTTP verb mechanism is unnecessary or over specified. The semantics of HTTP MEH are application-defined, so it can be used to fetch, update, replace, query, delete, or whatever the application defines to be necessary. Services honoring HTTP MEH implicitly signal that interoperability is not supported, but that intermediate proxies 'should do the right thing' regardless.
- james4k 14y ago> Verbose and repetitive error handling If you were to handle the same error cases in Python that you gave examples for in Go, it would probably be even more verbose with the try/catch wrapped around. > Errors passing silently – ticking time bombs to go Are you kidding? Unless exceptions are documented well, which they rarely are, this is a much greater problem when using exceptions.
- je42 14y ago> If you were to handle the same error cases in Python that you gave examples for in Go, it would probably be even more verbose with the try/catch wrapped around. Not sure what you are talking about: try: datastore.Get(c, key, record); viewTemplate.Execute(w, record); catch e: appError(e);
- wonderzombie 14y agoIs catching all exceptions really what you wanted to do here? A generic appError() is not equivalent to handling each possible failure more by type, as the Go example does.
- je42 14y agoMy bad. Good point. I looked at what he meant - not what he wrote. :)
- lmm 14y ago>Are you kidding? Unless exceptions are documented well, which they rarely are, this is a much greater problem when using exceptions. No it's not. If you get an exception you didn't prepare for, your code fails, not silently but very loudly. The argument to have is whether this is better or worse than your code silently continuing to do all the next steps even though the first one failed. (FWIW I agree with the article, but will say it's kind of surprising to see that point of view from a python programmer given the python approach to type-checking)
- hasenj 14y agoThe error handling style is certainly different - but it's not so bad. It's a matter of style and getting used to stuff. Maybe a better error handling mechanism is needed - but I'd prefer if it's not exception handling. Perhaps lisp style error handling? What ever that is - for I'm not familiar with it - but I keep hearing lispers brag about how awesome it is!
- mquander 14y agohttp://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html http://www.gigamonkeys.com/book/beyond-exception-handling-co... Think exceptions, but instead of blowing away the stack on the way up to your exception handler, it gives you the option of resuming execution in some way where you left off. It also lets you encapsulate error handling better.
- smithzvk 14y agoI am glad to see some CL condition system pop up here. This article basically turned me off from Go and reading peoples support for C style error handling has made me question what the heck they are thinking. There are actually several nice things about the CL system, but nothing there is going to convince someone that actually thinks not only that having every return value in your language serve the role of an error signal is reasonable, but actually arguing that it is equivalent from a programmers perspective or even a better method of programming.
- dchest 14y agoInstead of insulting people and declaring that you can't convince them, try posting some useful information about CL condition system and why you think it's better than everything else.
- smithzvk 14y agoFirst let me apologize for the length, second I will say that to my knowledge none of this is about the CL condition system in particular, third this is basically a brain dump because I need to get back to work and still don't understand the error code camps points... I definitely wasn't trying to insult but I recognize that I wasn't being helpful either. I was basically throwing up my hands. And while I like CL's system, I haven't come close to trying everything out there so I can't be a judge; I mean this not like a cop out but seriously, I am very poorly informed about what other languages have to offer. But since you ask: I guess if I was trying to convince someone that exceptions are inherently a better mechanism, I would start by exploring how arithmetic is handled in the C language. Let's say we have a function called "add" that adds two numbers. Typically we would think expect that the return value of such a function should hold the result of that addition. It would be naive, however, to expect add to always complete without something like an error happening. While addition of two numbers seems safe, it isn't when we start using our imperfect number representations such as floating point numbers (susceptible to overflow, underflow, loss of precision, and more) but also with machine sized integers (susceptible to overflow) and even bigints (what happens if we attempt to add two numbers and exhaust system heap space?). The way these are currently handled in the C language (to the best of my knowledge) is by three different mechanisms: 1) floats return special floats that say indicate that there was an error somewhere, like NaN (though you can probably instruct your OS/hardware/whatever to issue these as signals instead of silently spitting out an NaN), 2) wrap-around is loosely taken as a standard but truly it has undefined consequences, your code must guarantee that this cannot happen, and 3) the program will probably exit. This is messy but we have learned to deal with it and has become second nature to C programmers; but that doesn't means it is good. But this doesn't need to be the case, we could use the return value checking that is being promulgated by some here. We can define add as... error_code add(int a, int b, int *return) ...where int could be any number type and translate every occurrence of "a+b" in our code into something like... int ret; if (err = add(a,b,&ret)) { // err handling here } // ret holds the answer here I cannot believe this is preferable to anybody, in fact I am going to go out on a limb and say that it isn't. Instead we just trust this to work when we know that there are instances where it won't and, in the case of floating point math, it is extremely common for you to hit those cases where it doesn't work. If we were serious about writing code that handled these errors gracefully, our C code would be unmaintainable. If we were honest about the source of this laziness, I think we would say that this is due to the syntactic and mental overhead of the error handling. The fact that we don't code like this, even the very fact that C will spit out an NaN and propagate it along indefinitely, is proof in point that people dislike this type of "check the return value" error handling. This is C more or less pushing us to write code that is less robust. If we really wanted to consider what happens in a Common Lisp system, you would have a function "add" that takes two numbers and will always return the correct result of that addition or won't return at all. This means no wrap around overflow, no NaNs; this is vastly cleaner. However, as far as I know, this has nothing to do with CL in particular, this is presumably how Python or any language with a exceptions works. This is what I cannot fathom: that there are people that are willing to throw away this simplifying assumption because it means that your code might have controlled non-local transfer of execution. This is what Joel on Software directly says (linked elsewhere in this thread); exception handling is like goto, goto is bad, even when it is contained within an apparatus fixes its problems, like the exception handling apparatus. Or that people think the same amount of work is involved in either system? How does that make sense? Which way would you rather treat something like "add(a, add(b, add(c, d)))" because to my eye the C style handling adds a bunch of boiler plate code that I'd rather not write while adding little to no value. Like I was saying, I cannot fathom it so it is hard to form an argument against the alternative stance that I do not understand. Perhaps there are places where we want our execution so close to the structure of the code that handling errors C style makes sense, but I have to imagine that they are few and far between. If we compare a more common example where people will place error handlers: writing to a file. Is it better to have writefile(file, data) return an int that indicates error, or is it better to just say "writefile puts this data in file, period". Here are two errors that might come up when writing to a file, we don't have write permission for that file or the disk is full. Where do you want to handle these errors? One error happens when we open it, the other happens when we attempt to write to it further down the call tree, but presumably we might want to catch both and deal with them at the same location. With exceptions you catch/handle the exceptions you are interested in. When returning error codes you have to manually defer the error up the call tree, affecting the interface of every calling function up to the highest function that can actually handle the error. So you can add the boiler plate code to combine and propagate errors up the call tree to your task list now. At the root of this is the exact same issue as the previous point, it adds syntactic and mental overhead for the programmer. This article seems to have spawned some responses. Consider this post: http://www.yosefk.com/blog/error-codes-vs-exceptions-critical-code-vs-typical-code.html http://www.yosefk.com/blog/error-codes-vs-exceptions-critica... He makes claims like in the code... open_the_gate() wait_for_our_men_to_come_in() close_the_gate() ...if there is an exception inside wait_for_our_men_to_come_in then we'll never close the gate. While this is true for the code he's written, most people would (or should) consider that code buggy. In my experience and the comments of that post confirm it, most languages have a mechanism to ensure certain side effects happen even under non-local control transfer. Where are the points of intermediate state that he render exceptions equally bad as error codes? Are they the states that we explicitly account for in any bug free program, like closing the gate if there is an problem bringing the men in? I will reiterate, I truly am at a loss, this is not a rhetorical question. It seems like I am missing something. To sum up: One method seems like a clean method of dealing with errors that will happen, the other seems like a nightmare syntactically and basically consists of code that I deeply feel should be up to a compiler to write.
- ChuckMcM 14y agoHeh, I thought the answer was "Because my blog is named uberpython and it would be way confusing." :-) But error handling is one of those topics that really gets people going. From ABEND in the old IBM batch days, to uncatchable signals like SEGV in unix and uncaught exceptions in C++ or Java. I tend to come down on the "decide what you are going to do in the code, right where the error occurs" flavor of the argument. So checking for errors when they occur is important to me, what to do next gets to be sticky. So there are three things you want to do: 1) Easy - you just want to quit/die/exit pretend you never existed. This is pretty easy and most OS'es and embedded toolkits have something along the lines of 'exit' or 'exit_with_dump.' So that later you can try to reconstruct what happened. 2) Is "this is bad, but it might be ok later" where you want to unwind to the point where this started but not completely exit. Exceptions are pretty good for this if used well since you can catch them at the 'unwind' point and if you can attach the unwinding actions (closing files, freeing memory, etc) to the return stack then it can be managable. 3) You want to plod along in a degraded mode, which means you need some way of communicating with the rest of your code that you're damaged goods at some level. Go has an interesting mix which I haven't used extensively but I wouldn't dismiss it out of hand. The folks writing it run services that continue through partial outages so if it were too egregious folks would rebel inside of Google.
- sitharus 14y agoThe problem I found is #1 is hard. In C# if I want to just exit/return an HTTP error I ignore the exception and it happens. In Go for the majority of cases I have to check the error and call a panic function. I wrote a helper function to do this - it takes one parameter and panics if not nil. 2 and 3 I've found to be at little less verbose as there's no try {...} catch{...} around it. It seems a nice way of avoiding the stack sentinels or setjmp/longjmp that C++ has, which means the output is less architecture dependent. Personally I don't think Go is quite ready for prime time, but it's definitely promising. I'm doing a couple of projects with it to see how it runs.
- zmj 14y agoHave you seen the Error function in the HTTP package? http://golang.org/pkg/net/http/#Error http://golang.org/pkg/net/http/#Error
- tptacek 14y agoI'm not in love with this aspect of Go either, and I also find that the idiom for dealing with it (multiple return values and multi-statement if conditional clauses) doesn't play well with Go's scoping rules, so that I find myself regularly having to decide between cleaner conditional or an explicit variable declaration. I also don't love how it makes my code look like my teenage-years C code. But I also think this is a very silly reason to adopt or not adopt a tool. No other approach to error handling is less fraught. If Python is your only language, Go or something like Go is probably a very useful thing to have in your back pocket: compiled native binaries, fine-grained control over memory layout, and a simple and effective concurrency model is a good thing to have in one package. There are a lot of things that annoy me about Go (and for that matter Python, which irritates me for very similar reasons). What I tell myself to get over that and keep an open mind is, Go may not be my idea of an elegant language (yet; I'm still learning to appreciate it), but it is an excellent tool. I can get over the language stuff if the tool works well enough, and Go seems to for me. Incidentally, who "leaves" a language? I have a very hard time seeing how, even from the label on the tin, anyone could believe Go is a great solution for every problem. Python sure as hell isn't either.
- wonderzombie 14y agoWhat a refreshingly even-handed perspective. And you even talk about semantics instead of syntax. Go isn't particularly elegant in my opinion, either, at least not compared to a lot of languages. But it's pretty good for getting stuff done, and handles a lot of common problems in a way that is direct and simple. I can perceive that utilitarianism as a form of elegance.
- diego 14y agoIncidentally, who "leaves" a language? I started programming almost 30 years ago. Some languages I haven't touched in decades: BASIC, Fortran or Pascal. These days every time I have to use Java I hope it's the last. On the other hand, I used C for the first time 25 years ago. I don't use it very often but I wouldn't say I've left it. So yes, people leave languages. And in many cases for good reason.
- hughw 14y ago
- deleted 14y ago[deleted]
- crazygringo 14y agoIf you use a language that uses error codes, then you're forced to explicitly think about what to do in every single error case (or not, and accept the consequences). This can add a lot of mental overhead, but can result in a much more robust and well-thought-out program. But because more logic is involved period (to handle the robustness), the program is necessarily more complex. If you use exception handling, then suddenly catching errors becomes much easier, and writing code is faster, but it's not like your program is any more robust. If you catch an error up at the top, your program or function is probably just going to quit. Of course, you can add more code to deal individually with each of the exceptions in more robust ways, even to the point of wrapping each statement in its own try/catch block, but then it's no different in practice from returning error codes. In the end, I just don't see much of a practical difference. In both cases, if you want to be a lazy or rapid programmer, errors will lead to your software just not working. And if you want to be conscientious about your errors, then you can do that in both cases. Exception handling lets you handle errors "at a distance". It's convenient, but harder to keep track of. Returning errors keeps your code execution where you can see it, and you always know where it is. It's simpler, but more verbose.
- babarock 14y agoPossibly relevant: http://www.tratt.net/laurie/tech_articles/articles/a_proposal_for_error_handling http://www.tratt.net/laurie/tech_articles/articles/a_proposa...
- belorn 14y agoI would guess that most (99%) of C programmers are lazy or has an rapid programming style then. Its a rare thing to see C source code that even do the simple thing like wrapping every write/read/print call with a loop that detects eintr. In my years of programming, I have yet to stumble on a other programer who even knows that one should be doing this. Looking at c libraries and their example code, almost every time, the example lacks error handling. Its almost like people avoid doing error checking in C code because of the cumbersome amount needed to be done in order to check each and every time. If an example code would include error checking, the number of lines can easy increase 3-4 times the original size.
- knodi 14y agoThis kind of error handling is the best part of Go.
- ericbb 14y agoOn the topic of when to use error-returns vs. when to use panic: I struggled with this aspect of Go coding too and what I decided was that, in order to choose your approach to errors, you should think about how important it is that your function can be composed into an expression: x := foo(a) + bar(b) vs. c, err := foo(a) if err != nil { ... } d, err := bar(b) if err != nil { ... } x := c + d It's a trade-off. By using "panic", you enable more concise and compositional code. By using error-returns, you are being more explicit and making it easier for the caller to handle errors at the call-site. Usually, functions that can fail are not the kind you desperately want to use in expressions anyway. So error-returns are more common. But there are many situations where errors can happen and where composition is a big win. In those cases, you reach for "panic". If you look at some of the built-ins that can panic--I'm thinking of array-indexing and division and regexp.MustCompile(), for example--you notice that these would become very cumbersome if they used error-returns instead. There's also the issue of initialization. That's the justification given in the case of regexp.MustCompile(). An initializer must be a single expression. The words "panic" and "error" probably lead people to think that you should make the decision based on severity but I don't think that's right. (That's just my take on it. I'm no Go expert so there are likely better ways to think about it.)
- wonderzombie 14y agoAs I understand it, it is about severity. I think "panic" and "error" are meant to handle situations where there is no obvious, correct answer, like with array indexing. A nil pointer is another gimme example. Some languages have unchecked exceptions — maybe conceptualize it somewhat like that. panics() are for disasters, for serious program errors. By contrast, a network timeout is not a disaster. It's not "normal" or desired behavior but it's well within widely-known modes of behavior. Composition is nice-to-have but in general you ought to be able to write Go as if everything that does not return errors will succeed under the vast majority of circumstances, or where failure is inevitable. Recover is, among other things, a failsafe of sorts for cases when code you do not control (e.g. a library) panics. It might be catastrophic failure for the library, but it may not be for your code, and you need an escape hatch. ...I am not an expert, either, but I've spent a fair amount of time on the Go mailing lists. I feel like I'm actually repeating something I've read, but Effective Go doesn't talk much about this. (It does say that library functions should avoid panic.)
- belorn 14y agoWhen dealing with C and error handling, I have rather simple rule. If in the process of writing a simple program (+1k lines), and you did not find a bug in the libc documentation, then you are not looking hard enough at return values. I do not have the same rule for python.
- chrismorgan 14y agoI see both pros and cons in the Go approach. I find Python's exceptions model to be something that does occasionally frustrate me. For example, I'm used to using NSIS for some things (the PortableApps.com Launcher being one thing); its model is that a command may set the global error flag. This means that some sorts of things will fail silently - occasionally bad, but generally good. Some styles of Python scripts (generally automation scripts rather than full-blown programs) would do much better ignoring most errors. When you have a script merrily chugging along and all of a sudden due to some obscure corner case (perhaps not even documented) in a small, incidental part of the script the whole thing quits, it can be rather annoying. The Go approach lets you catch errors if you want, or drop them, much more easily than Python approach. My inclination at present is that the Go approach is more likely to end up with error-resilient code, as it encourages dealing with errors each time, while Python typically encourages you to let the caller deal with an exception if you can't - but often I think the caller doesn't realise that he should. Or after a couple of levels of indirection, it's not even documented that that exception can occur. (This sort of thing, I believe, is something that Java's checked exceptions were supposed to help with - either deal with the exception or declare it.) For myself, I've had lots of experience with Python and am now dabbling with Go, using it for two smaller projects to decide which to use for my next major project. At the very least, I think the approach is worthwhile trying in a serious project that its merits and those of the Python approach may be more completely analysed.
- agentS 14y agoMaybe its just me, but I like the fact that adding exponential backoff to an HTTP request goes from http://play.golang.org/p/8WQ8XXrKw0 http://play.golang.org/p/8WQ8XXrKw0 to http://play.golang.org/p/3tCiNhAfo3 http://play.golang.org/p/3tCiNhAfo3, in no small part due to the explicit error-handling style of Go.
- elimisteve 14y agoI write Python and Go code every week, and have since I started using Go almost 2 years ago. I appreciate Go because I'm working hard to become an engineer who builds robust software rather than a sloppy hacker that throws scripts together. One significant difference between the former and the latter is carefully handling errors versus not. Python's error handling seems much more succinct only because most of us don't both handling errors at all! Every other line throws many exceptions, but we ignore this for convenience. (Those ugly "except ___:" statements ruin our oh-so-cool one-liners!) Bottom line: When I feel like having fun making something simple and getting it done _fast_, I use Python. When I feel like building something that _needs_ to work -- especially anything that does more than one thing at a time, or should use all cores efficiently -- I use Go. And yes, that means taking error handling seriously.
- barrkel 14y agoExceptions are like garbage collection. Garbage collection relieves you from the drudgery, accounting and bureaucracy of manual memory management and indirectly leads to more expressive forms of programming once function boundaries are freed from having to specify who owns the data being passed back and forth. But you still need to be aware of space vs time usage, you still need to know where memory is being allocated, used and kept alive in your program, and you can still have "leaks" where a data structure is rooting too much dead data. This isn't immediately obvious to the novice, but that isn't IMHO a good reason to dislike the "magic" behaviour of GC. In the hands of someone who knows what's going on, programs get much simpler. Exceptions relieve you from the drudgery, accounting and bureaucracy of manual error passing and recovery, and indirectly leads to more expressive forms of programming once function boundaries are freed from having to specify how rich error conditions are passed back along the chain of callers. But you still need to be aware of errors that require aborting and errors that can be fixed and resumed. You can still ignore exceptions that leads to programs crashing when they shouldn't. Looking at a program using exception handling, it sometimes isn't immediately obvious to the novice how the exception plumbing is working (especially since you don't usually see it at the function call level), but that isn't IMHO a good reason to dislike the "magic" behaviour of exceptions. In the hands of someone who knows what's going on, programs get much simpler.
- ryanmolden 14y ago>In the hands of someone who knows what's going on, programs get much simpler I never really got this argument. With RAII types that have value semantics (ala shared_ptr) what is so difficult about memory management in C++? I guess there are reference cycles, but weak_ptr can help there. Manual ref-counting (like a COM AddRef/Release pattern) can be tricky, but that is where attention to detail, code reviews and basic competence come in. RAII wrappers with value semantics help here too. Most problems I have seen with that have been people simply not bothering to learn even the basics or using "smart" objects without understanding how they work or how to use them properly, which really isn't that hard, honestly. >that isn't IMHO a good reason to dislike the "magic" behaviour of exceptions. Agreed, my primary pain point around exceptions is people that write functions that mutate some state and are interrupted in the middle via an exception and stack unwind leaves objects in some franken-state. Transactional semantics in the face of exceptions don't just happen except in trivial cases (i.e. functions that don't mutate state or have trivial unwind semantics where they must only restore say a single mutated value). That said the same problem exists anytime there is state mutation and multiple, failure exit paths. For some reason I guess the code I have seen using error code return style tends to handle this better. Could just be some kind of selection bias.
- tikhonj 14y agoAs usual in these threads about Go, I really wish people would consider Haskell as a nice alternative. A lot of people write Haskell off as "academic" or "impractical", which I feel is not an entirely fair assessment. Particularly: Haskell is fast, concurrent by design (whatever that means, I'm sure Haskell is :P), typed but not cumbersome or ugly (less cumbersome and ugly than Go's types, even) and--most importantly for this article--does error handling really well. In a certain sense, Haskell's main method for handling errors is actually similar to Go's. It returns a type corresponding to either an error or a value (this type, naturally, is called Either). This method is special, oddly enough, because it isn't special: Either is just a normal Haskell type so the error checking isn't baked into the language. Coming from another language, you would expect to have to check your return value each time. That is, you fear your code would look like this pseudocode: if isError val1 then pass val1 else val2 <- someFunction val1 if isError val2 ... However, this is not the case! The people implementing Either noticed that this was a very common case: most of the time, you want to propagate an error value outwards and only do any work on a valid value. So you can actually just write code like this: do val1 <- someValue val2 <- someFunction val1 val3 <- someFunction val2 someFunction val3 then, if any of the values return an error, it gets propagated to the end of the code. Then, when you want to check if you actually got a valid value--maybe in some central location in your code, or wherever is convenient--you can just use a normal case analysis. Additionally, while the errors do pass through essentially silently, the type checker does ensure you deal with them at some point before using or returning them. If you ever want to get the Int from a Either Error Int, you have to either handle the error case somehow or explicitly ignore it (e.g. with a partial pattern match). The latter option will generate a compiler warning, so you can't do it by accident without being notified. So the mechanism is simple, but it can also stay out of your way syntactically. So what else is this good for? Well, it's just a normal data type, nothing special; you can use existing library functions with these values. For example, you can use the alternation operator <|> to find the first non-error value: val1 <|> val2 <|> someFunction 5 <|> ... This is often a very useful idiom which would be harder to write with a different way of handling errors. There are more utility functions like this (e.g. optional) that let you make your intents very clear at a high level. The optional function, for example, lets you do exactly what it claims: you can mark a value as "optional", meaning that any error from it will be ignored rather than propagated. You can also layer on this error-handling logic on other similar effects. For example, there are some types (like LogicT) that represent backtracking search. Combining error-handling with nondeterminism gives you a question: should an error cause the whole computation to fail, or only that particular branch? The beauty is that you can choose either option: if you wrap LogicT with ErrorT (this is just like Either except it can be combined with other types) an error will cause the whole computation to fail; if you wrap ErrorT with LogicT, the error will only cause the current branch to fail. This not only makes it easy to choose one or the other, but also makes it very clear which one you did choose: it's right there in the type system in a very declarative fashion. Haskell also has a bunch of other advantages which aren't worth going into here. I think anybody looking for something like Go--a fast, high-level, concise, typed alternative to Python--should definitely consider Haskell. While it may not seem so at first, I believe that Go and Haskell are good for a very similar set of problems and so actually overlap quite a bit. If you really dislike some particular things about Haskell, you should also consider some similar languages like OCaml and F#. I personally prefer Haskell, but there are definitely good cases to be made for either of the other two.
- jorgem 14y agoOut of curiousity: Aside from "returning errors" and "throwing exceptions", is there any interesting research into other ways of error handling?
- ufo 14y agoCommon Lisp has an interesting exception system where errors have a chance to be handled without unwindind the stack. Basically, functions also receive an "error handling" object as an extra argument and they consult when they encounter an exception. The error handling object then edcides wether to continue operation, or to unwind back.
- masklinn 14y ago> Common Lisp has an interesting exception system Called "conditions", fwiw. Smalltalk has a very similar system (the main difference is in the declaration of restarts)
- dvhh 14y agoI am eternally asking myself how do people handle exception in multi-threading programs ? Not that error code solve the issue at all, but it look easier (less complex) to handle thread error with error code
- luriel 14y agoWell, given how hopeless it is to try to do threaded programming with Python, I guess that is not a consideration Python programmers usually worry about.
- usea 14y agoThe same way you handle error return values in multi-threaded programs. Exceptions don't mean you simply ignore all errors all the time. They're just a different mechanism for communicating failures across function calls. You can code with exceptions the same way you do with errors. Really the two styles of error communication are very similar. The default behavior for exceptions is to abort and let the parent deal with it, but you have the option of continuing on instead (catch). The default behavior for returned errors is to keep going, but you have the option of aborting and informing the caller. In either case you have to examine the code you're calling or its documentation to find out the details of what could happen when things go wrong. Exceptions have the advantage of the abort-behavior requiring zero code, but it may be less obvious to beginners what is going on. In either case, people just don't like to be surprised, so they prefer what is familiar. In C#, which has Tasks for concurrency (very similar to goroutines), an uncaught exception in a Task will bring down the entire program at some non-deterministic time, unless you make sure to observe it somewhere. So generally you're using some extension method so that invoking doSomething() in a Task looks like: Task.Factory.StartNew(() => { doSomething(); }).OnException((ex) => { /*log it*/ }); Either that or you just make sure to catch anything at the root of your Task block.
- mseepgood 14y agoOf course there is a difference between an "index out of bounds" error and a network connection error: one is a programming error and the other is not.
- exabrial 14y agoMy opinion: I'm not a fan of Python. * It's really slow. * Most people write their code statically, but it's dynamically typed language anyway. * Things like '__init__'... what? * Having something you can see (whitespace) be significant is a bad idea. * Even worse, the community likes to use spaces instead of tabs, which makes the 'significant whitespace' a significant problem. * With the exception of Flask, important design patterns and lessons: DRY, SOC, DI, SRP, etc are largely ignored and called "Java-like." Anyway, that's my $0.02
- lmm 14y agoSure, python is slow. For problems where this matters, there are usually python libraries available that will do it much faster than pure python (e.g. for scientific number crunching you use numpy, which is using FORTRAN behind the scenes). There are probably some problems where performance is critical and no libraries are available, but not many. Sure, python is dynamic; at the time it was first created it was hard to provide the level of flexible ("duck") typing python has in a static environment. Now that typing has advanced, we are starting to see some level of type annotation support in python; I would expect it to get static typing eventually, but we can't break existing code. Still, I can totally understand leaving python for a static language with good type inference, like haskell or scala. I don't think go's type system is powerful or elegant enough though. __init__ is fantastic; it removes all the special cases around constructors you see in other languages, and makes it "just another method" that follows all the usual method resolution rules etc. This means less to learn, less to bite you, and much easier to e.g. refactor a constructor into a factory method (or vice versa). Whitespace isn't significant, indentation is. And when you look at a piece of code, you can see the indentation far more quickly and easily than you can see the braces. At least I can. DRY, SOC, SRP are very much part of the python philosophy. I find python's standard library is much better at following "tell, don't ask" than most languages'. I agree that many python programs should follow the DI principle better and possibly the language should have better support for it, but IME the java/spring-approach as it's usually implemented doesn't really provide DI, it's just a complex reimplementation of singletons. I've yet to see a language or framework that can really help programmers do DI right if they don't already know.
- pavanky 14y ago>That problem is errors are handled in return values. 70′s style. >This is one of the things I can’t stand in C. Having to check error values for every function call is indeed a pain. But C has macros and I think it is a nice and elegant way to handle errors. I personally prefer MACROS to exceptions when writing C/C++ code. But to each his own. Does anyone know if go supports macros ? If they do not, it is one ugly problem to have! EDIT I just realized you can write custom exceptions that can provide similar information about line numbers, functions etc. So removed a line saying that was a plus for macros.
- dschiptsov 14y agoThe principle behind Go's design decision is based on actual experience and need to be understood by amateurs. The idea is quite simple: errors are not some rare special cases, they are ordinary, general events. That means there are no need to some special mechanism for handling them. They should be handled as ordinary events within common FSM. There is no contradiction in using the general mechanism of examining returned values to determine the state of procedure execution. Any FSM requires if (or cond) statements. It is the essence of a program's logic. The mechanism of exceptions was wrong. It is wrong in Java, it is wrong in C++. It is just a bad design. The good design is using the same mechanism the underlying OS uses. In case of UNIX-derivatives it is the explicit checking of a return value. Because errors are common and ordinary events. Amateurs believe that errors are rare, and they will avoid them. This is over-optimistic - in practice errors are of nothing special. It is just an alternative branch of a condition, where a consequent assumed to be a success.
- cletus 14y agoI have mixed feelings about errors as return codes. Then again, I have mixed feelings about exceptions. There are two general use cases for exceptions: 1. Unexpected (typically fatal) problems; 2. As an alternative to multiple return values. (1) is things like out of memory errors. (2) is things like you're trying to parse a user input into a number and it fails. I despise (2) for exceptions. It means writing code like: try: f = float(someText) catch ValueError: # I just parsed you, this is crazy, # here's an exception, throw it maybe? where this gets particularly irritating is when you start writing code like this: try: doSomething() catch ValueError: pass I nearly always end up writing wrapper functions around that crap. Java is worse for this because some libraries (including standard ones) abuse checked exceptions for this. I actually prefer: if f, err := strconv.ParseFloat(text); err != nil { // do something } or even: f, _ := strconv.ParseFloat(text); for this kind of scenario. For the truly bad--typically fatal--error conditions and cleanup, IMHO defer/panic actually works quite well. I certainly prefer this: f := File.Open('foo') defer f.Close() // do stuff to: try: f = open('foo') # do stuff finally: if f: f.close() as Go puts the two relevant things together. Don't get me wrong: I like Python too but I do think Go has a lot going for it and has a bright future.
- vog 14y agoPython doesn't have defer/panic, but it does have "with" statements which solve this issue in a RAII-like style: with open('foo') as f: # do stuff
- praptak 14y agoAd your last Python example - the preferred method of doing resource cleanup in Python is the 'with' mechanism: with open("x.txt") as f: #do stuff with f That's it. Obviously, compared to 'defer', it hides stuff - the actual magic happens in special methods __enter__ and __exit__ of the object passed to 'with'.
- masklinn 14y ago> Obviously, compared to 'defer', it hides stuff - the actual magic happens in special methods __enter__ and __exit__ of the object passed to 'with'. Then again, `f.Close()` could hide any "magic" you want as well.
- jonmrodriguez 14y agoSomeone can fix this by creating and popularizing a simple preprocessor that implements a macro called "safely" that expands to something like "call this function, and then if it errors, report the error to STDERR and abort execution". Then just prefix the majority of your commands with "safely", and implement your own error-handling in the few cases where you care.
- yason 14y agoOn the other hand, us who haved loved and embraced C don't mind this at all. It's up to us how to arrange the error handling. Different situations warrant different strategies. Sometimes it's ok to return NULL, sometimes it's ok to return true/false and pass the actual result back indirectly, sometimes it's ok to return the value but use an external error flag. Sometimes it's ok to mix control flow and external flagging of errors, namely what an exception is, effectively. However, in my opinion, languages who point too much to one single error handling mechanism are more irritating than languages that leave it up to the programmer. I'm particularly wary of exceptions per se: they're a really nice, clean concept but on the other hand I rarely hit an use case that would be a perfect hit for exceptions, and even those perfect cases for exception-handling wouldn't look too shabby if designed with other error handling strategies. On the other hand, many cases where errors are handled with exceptions get awfully ugly in the normal case. For example, the "try: ... except <name-of-error>: pass" idiom in Python. I can't count the number of times I've written a small wrapper function around a trivial exception handling case, that just flattens the result and error into a suitable value if that fits my use case.
- hiphopopotamus 14y agoPersonally I think the community might be spending too much time on Python 3. Python 2 is fine, and the upgrade will offer few significant advantages for most people. I think the effort would be better spent working on things like frameworks and tools for web development.
- Tichy 14y agoWhat happens in Go if there is an exception (division by zero or whatnot)? Does it just exit, no stack trace? I am so used to exceptions that I don't even know how it used to work without them. I think getting a stack trace for debugging is really important...
- rogpeppe1 14y agoYou get a panic, which can be caught. http://play.golang.org/p/IaTAGizhrF http://play.golang.org/p/IaTAGizhrF