5 ms·
I 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. Une
by cletus 14y ago
I 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.
- vidarh 14y agoRuby by convention uses blocks for the same situation: def withSomeResource handle = acquireSomeResource yield handle ensure freeSomeResource(handle) if handle end withSomeResource do |h| h.whatever end File.open and other parts of the standard library does that by default. I imagine the Python implementation of "with" is pretty similar, given that it can be implemented pretty much like this in Ruby, and "(begin) .. ensure .." is pretty much equivalent to "try ... finally ..": def with r r.__enter__ yield(r) ensure r.__exit__ end
- praptak 14y agoThe two mechanisms are not very similar. With the blocks mechanism (if I understand correctly) the resource itself implements a method "execute this code and clean up yourself". This only uses the standard mechanisms of the language (i.e. blocks.) The control is with the resource. Python's 'with' inverts the control - the control is with the language runtime and the resource is passive here. In fact 'with' is a special syntax extension, with calls to hard coded method names (__enter__ and __exit__) done by the Python runtime. The resource itself (or the contextmanager representing it) only implements __enter__ and __exit__ and is passive with regards to this mechanism.
- vidarh 14y ago> With the blocks mechanism (if I understand correctly) the resource itself implements a method "execute this code and clean up yourself". Where the method is doesn't matter, e.g. the (incomplete) example I gave of implementing "with" in Ruby that shows a method that can be defined in the global "main" scope or wherever you want. Some classes implement class-methods to instantiate an object and pass it to a black and free the resource as a convenience, such as File.open, but it could go wherever else. The resource itself most certainly does not need to know a thing about it as long as its API lets you do the cleanup you want/need. > The control is with the resource. The control is with whatever calls yield. Whether that be a method on the global "main" object (closest thing Ruby has to a global, freestanding function) or on the class of the resource itself, or an explicit "begin ... ensure ... end" block if you only need it once. > In fact 'with' is a special syntax extension, with calls to hard coded method names (__enter__ and __exit__) done by the Python runtime. Which is why I gave my example of how I thought "with" would look like in Ruby (as it turns out I missed some exception handling in order for it to be equivalent to the Python version in functionality).
- klon 14y agoJava 7 has AutoCloseables for resource cleanup: try (FileInputStream f = new FileInputStream(path)) { // do stuff }
- dvhh 14y agoAbout fucking time
- draven 14y agoThe python mechanism is more general, you can use the /with/ construct with locks for example.
- andrewaylett 14y agoYou can do the same with Java. Both are in a sense trying to re-gain the most useful parts of RAII semantics from C++: making it much more difficult to forget to clean up resources after you've finished with them.
- draven 14y agoOr the unwind-protect faciliy from Common Lisp, that is "cleanup stuff before it goes out of scope"
- ubershmekel 14y agoI agree that exceptions can be cumbersome at times. And I do prefer the Go parsing float example to the try/catch one if my entire program is only one line. But in order to live in peace you have to either: * Force every programmer to always check all the error values. or * Allow errors to pass silently. So either you accept Go as a heavy-duty, heavy boilerplate error handling laden language. Or you accept it as a flaky, risky language. Exceptions are the better compromise in my opinion. You get much cleaner code. If this is the more common pattern for your programming life: try: f = float(someText) use_it_here(f) use_it_there(f) catch Exception: # I wanted to do things, but something went wrong. # Here's what to do now, this is easy Then you're better off using exceptions. If you need to check every single call (e.g. NASA or Kernel programming) then you might prefer this: if f, err := strconv.ParseFloat(text); err != nil { // do something } if f, err := use_it_here(text); err != nil { // do something } if f, err := use_it_there(text); err != nil { // do something }
- nickbp 14y agoSo, I rarely see eg Java code that actually handles the exceptions it receives. Usually its just "Oh, I got an exception, so I'll print an error (or silently fail) and blindly keep going". Given this, I don't see how Go is any worse with respect to sloppy programmers. They'll Always Find A Way. Also, why couldn't your second example be nested? It seems like either of those two examples could be structured identically to the other.
- lmm 14y agoI find Java exceptions quite good for cases where you want to fail at a much coarser level than the specific problem, but finer than the whole program. E.g. my previous^2 job was essentially a message-processing system; it had a bunch of loops that took messages off queues and processed them one at a time. If processing any given message threw an (uncaught) exception, it marked that message for retry or as failed and carried on. You could certainly argue for handling each message in a separate process a la erlang, but this approach worked well for us. I think checked exceptions were a mistake (IIRC Gosling agrees), and Java could do with better support for multiple return values (which exceptions get abused for), but I like Java-style runtime exceptions.
- gnuvince 14y agoOne problem with Go is that is uses multiple return values to indicate errors instead of alternative return values. When you call strconv.ParseFloat, you always get back two values, the error code and... wait, what float do you get back when there's an error? If you're going to use return values to indicate errors (and certainly I feel that there are reasons to use them, exceptions vs error codes is not an either/or proposition IMO), you should use sum types and return either an error or the correct value and have a mechanism to make sure you handle both cases. ML and Haskell get this right, it's a shame Go overlooked them in this regard.
- _ak 14y ago> One problem with Go is that is uses multiple return values to indicate errors instead of alternative return values. When you call strconv.ParseFloat, you always get back two values, the error code and... wait, what float do you get back when there's an error? Why would you even be interested in the float when there's an error?
- masklinn 14y agoThat's his point: why do you get something typed as float back when there's an error? And what does that thing mean?
- _ak 14y agoBut if there was an error, WHY WOULD YOU EVEN BOTHER?! Read the documentation what it says about the value in case of an error, and stop inflating a non-issue.
- papsosouid 14y agoHuman beings are capable of making mistakes, that's the entire point. If you forgot to check the error code in go, then you will end up using that non-float float that shouldn't exist as if it were really a float. In a decent language, you get either an error or a float, you have no way to accidently use the float if an error occurred.
- flyinRyan 14y ago>try: >> f = float(someText) >> catch ValueError: >> # I just parsed you, this is crazy, >> # here's an exception, throw it maybe? No, you have it exactly backwards. It's with return values that you have to check after every call to be safe. With exceptions you can truly say: if I don't know what to do if this fails then I don't do anything. You can let the exception bubble up higher, all the way up to crashing the program if you like (which gives a nice stack trace that can then be debugged). With return values, if you don't check then you could be building up more and more trash and you won't even know it until you either hit a point of code that finally does look at what's returned, or if an exception occurs (e.g. segfault).
- Edootjuh 14y agotry: f = float(someText) catch ValueError: # I just parsed you, this is crazy, # here's an exception, throw it maybe? Formatted That For You
- deleted 14y ago[deleted]
- Evbn 14y ago"Not checking" is just shorthand for pythons " except: pass".
- prodigal_erik 14y agoBut not checking is invisible. I can't grep for it. It doesn't stand out in a code review. It's almost never what I meant to do, so it shouldn't have been the default or easily overlooked, much less both.