9 ms·
I've spent the last year working on a Go system, writing Go every day. After a couple of months, I just stopped seeing/worrying about "if err != nil {". It has
by marcus_holmes 7y ago
I've spent the last year working on a Go system, writing Go every day.
After a couple of months, I just stopped seeing/worrying about "if err != nil {". It has become punctuation, the Go equivalent of semicolons;
I don't even use snippets; I manually type that every time. Which is good, because it does make me think about whether this function call can error, and what I should do about it if it does. 90% of the time it's ("just pass it up"), 5% of the time it's ("nothing, I don't actually care if this routine fails") but 5% of the time it's ("right, yes, this needs to be dealt with here").
When reading code, I just skip over it if it doesn't do anything interesting.
I understand that in English reading, we don't notice "they said"; our brains just skip over it, and you can use "said" to open every quote and it won't feel repetitive. That's how I've got with "if err != nil {"
so... I'm against this "try" stuff, because it feels like it's been proposed by people who haven't worked with the language too much. I'm not against making it more friendly for new people to learn, but this doesn't feel like that. This just feels like "ugh, really? I have to handle all the errors?!"...which is not a reason to change it.
- pjmlp 7y agoI used to write code like that during the 80 and 90's, until settling down in languages with first class support for exceptions. So yeah, I did it for around 20 years, and don't miss it.
- jchw 7y agoI don’t like exceptions at all. They do not make it obvious what is going on, I never am completely sure if I’m handling them right. When I want to throw an exception I’m often unsure which would be right, and sometimes your API can have multiple reasons to throw the same exception. Go error handling is not like that. But I can sure as hell say fairly that Go error handling is likely also not similar to what you did for 20 years in the 80s and 90s either. Rob Pike and friends surely knew a lot about what programming was like at that time. I, being relatively a youngster, don’t first hand, but I can tell you my experiences with C++, PHP, Python have not been nearly as good as Go with error handling. For one thing, C++ has no rigid standard for how to handle errors. Some people use exceptions, some used error methods on classes (including the standard library,) some used special integer or enumeration values (...including the standard library,) and some had libraries and frameworks have their own magic error handling mechanisms. This cognitive overhead was horrible. C wasn’t much better; atoi is a case study in why error handling in C sucks. Libraries that tried to standardize it, like SDL, were bearable if it was all you used, but it probably wasn’t, and some APIs, like Win32, made it even worse. (And I suppose it is worth at least mentioning setjmp/longjmp error handling. I don’t think it’s necessary to comment on why it’s not good.) Python exception handling is admittedly better, but its not really wonderful. Exception handling code in Python is prone to breakage that is first detected at runtime. If a function implementation changes, and the set of exceptions it might throw changes, that’s an invisible API change that may cause an unhandled exception in production. Not so great. Also, on a vaguely related note, you can’t really do error values using multiple returns like in Go, because Python doesn’t support multiple returns, only tuples, and refactoring between returning values and tuples is likely to run into accidental runtime errors (though you can paper over this issue a bit with type checking.) PHP error handling sucks, I will withhold from elaborating. All of the exception handling mechanisms suffer from one problem I really don’t like: it’s another nearly invisible part of the API. It makes the wrong thing (not handling errors) easy, and the right things (handling the appropriate errors correctly) hard. Your dependencies have to care about your call tree, and if it changes in refactoring it’s anyones bet what kinds of exceptions your function might throw. You could catch all exceptions, but because language errors like syntax errors (JavaScript) and index out of range and property name errors (Python) can also be exceptions, you rarely want to catch all exceptions. Not to mention, your call itself would be caught, so any exceptions caused by anything else in the try block would also be conflated. Go does some things that are mostly not new, but haven’t all been packaged together this way before exactly: - Custom errors via implicit interfaces, allowing easy, arbitrary data to be passed through errors while maintaining full control of error messages - Deep separation of programming errors and operational errors tend to be passed as error values. Programming errors, like indices being out of bound or misusing an API, typically results in a panic, whereas operational errors. Very seldom do you actually care what the error is, but when you do you can inspect the error as any other value, because it is just any other value. - Ecosystem-wide standards for how to pass errors. It’s almost 100% universal that errors are passed at the end of the return list. This makes it easy to parse for humans and easy to lint for machines. Linters can warn you about unused error values, and if a function suddenly has an error return it’s an API break, forcing you to fix existing code to properly handle errors. - Good library support for error types, including fmt.Errorf for one-off errors that don’t need special handling, and (third party) a myriad of error wrapping/helper libraries. They’re not needed at all, but can be quite handy. It works a lot better imo. You have more ceremony but less guessing. You can read a function and see almost every edge case, and when all of the functions perform good error hygiene you no longer need to guess about what refactoring your code will do. I’ll take my repetition.
- pjmlp 7y agoYeah, until one realizes that most Go error handling is a mix of - Parsing error results inside strings - if .... else boilerplate - Underscore everywhere to silence them - Abuse from panic, aka exceptions in disguise If you want error handling without exception's guesswork, there are checked exceptions (used for the first time in CLU 1975), and result types (used for the first in ML in 1973) Both without the ceremony that Go shares with Algol derived languages before exceptions were a thing. Rob Pike and friends surely do know a lot about what programming was like at that time, but they are also very opinionated on what they impose on others. Just because they have a very good career, it doesn't make them always right. I tend to think for myself and not from opinions of others.
- marcus_holmes 7y agoYou've read a lot of very bad Go code then, that doesn't follow any of the established practices for it. Your list there is almost a primer of "what not to do in Go", and is certainly not representative of the Go code in e.g. the standard library. I sense some frustration about the language... what happened to make you so anti-Go?
- pjmlp 7y agoYou mean like this standard library code? https://go.googlesource.com/proposal/+/master/design/go2draft-error-inspection.md https://go.googlesource.com/proposal/+/master/design/go2draf... > I sense some frustration about the language... what happened to make you so anti-Go? Go is C with GC and bounds checking, aka Limbo reborn with some Oberon-2 influence. Already much better for our IT safety than sticking with C, still I kind of expected Google capable of producing Swift, Rust or TypeScript level of language design, given their pile of PhDs.
- deleted 7y ago[deleted]
- marcus_holmes 7y ago
- lanstin 7y agoSo much this: "it does make me think about whether this function call can error, and what I should do about it if it does." This thinking is why C programs are the most reliable computing substrate - because errors can happen and there is no substitute for the programming taking a moment to think the possibilities of error. Build that thinking into the dev process, and you have a hope of reliability within the process (which doesn't mean anything else is reliable, but at least your call stack is.
- pjmlp 7y agoYeah, they are so reliable. A living proof of good quality. https://www.cvedetails.com/vulnerability-list/opmemc-1/memory-corruption.html https://www.cvedetails.com/vulnerability-list/opmemc-1/memor...
- lanstin 7y agoLinux hasn't segfaulted on me in years. Nor has Python C level (except when I use ctypes to use openssl directly). Nor has gcc, emacs or any of the standard shell commands I use. When I worked in a large C shop writing network servers, we all got emailed whenever our code segfaulted with a stack trace. Now our Java servers routinely catch all exceptions and then need to be restarted for some reason when the network glitches. I wouldn't use C for high velocity business logic ("alternating soft and hard layers") but I'd absolutely use it for stuff that needs reliability. I'd use Go now also.
- pjmlp 7y agoReally?!? Then you should advise Google on what they are doing wrong. "The State of Kernel Self Protection Project by Kees Cook, Google" https://www.youtube.com/watch?v=aMkCKeZ8xZw https://www.youtube.com/watch?v=aMkCKeZ8xZw