12 ms·
Popular C software is so reliable because of enormous amount of effort spent developing, testing, and polishing it over many years - not because error handling
by azov 13y ago
Popular C software is so reliable because of enormous amount of effort spent developing, testing, and polishing it over many years - not because error handling in C is superior to that in higher-level languages.
If your python script sticks around for 30 years constantly being used by millions of people - I bet it will be rock solid as well. All other things [1] being equal [2], a program written in a higher-level language will likely be more reliable - simply because it will likely be shorter. C has many properties conductive to great longevity of software written in it, but error handling is not one of them.
Now, to the rant about not knowing the exact number of exceptions a function can throw. Most sane languages [3] give you exception hierarchy. You know that every function can throw an Exception (or Throwable, or whatever the root of all things evil is called in your language), and you either handle it or let in propagate. The important bit is you catch exceptions based on what you can handle in a current context, not on what the functions you call can throw [4]. As long as you realize that every call can potentially throw, it is not important what exactly is thrown - letting you abstract away the exact nature of an error is a feature, not a bug, that's what exceptions are there for! Let's say some function deep below changed and now throws a new exception type. So what? It didn't change what you can handle, so there's no reason to modify calling function - just let the exception propagate. What if nothing in your program can handle that exception? You let it crash (hopefully to the debugger, and no, it's not ironic that I suggest it in a topic about reliable programs - crashing is a reliability feature invented to expose problems that would be hidden and hard to debug otherwise). Of course you may also put a few catch-all clauses in a very limited number of strategic places.
If you approach exceptions this way they are vastly superior to error codes.
[1] Scope of the problem, amount of effort spent on development, programmer skills
[2] Or at least on the same order of magnitude
[3] With a notable exception of C++ - I'm not sure what they were smoking when they decided that throwing arbitrary values is a good idea
[4] And this, by the way, is what's wrong with checked exceptions in Java
PS. Didn't mean that having no exceptions is insane - but that if a language has exceptions, the sane thing to do is to organize them in a hierarchy with a single root.
- deleted 13y ago[deleted]
- ante_annum 13y ago> Most sane languages [3] give you exception hierarchy Go being a glaring exception -- and this is a good thing.
- apr 13y agoHe said sane.
- flogic 13y agoI disagree. Go errors aren't even error codes. The error interface is just one method that returns a string. The only advantage Go has over C is that the errors are no longer inband with normal return values. Once you get one though, you're back in the perl5 land of reasoning about your error by scraping a string. I'm completely baffled as to why they didn't attempt to bundle more machine readable information into the errors.
- mfenniak 13y agoGo errors are machine-readable data. Most modules expose variables with the naming convention Err... that represent the errors that they can return. The errors can be compared with those values (eg. err == os.ErrPermission). Some modules also define error types that provide additional error data, such as net.OpError; a type-cast can be used to check if the error is of that type, and then access the data within. As a fallback, all errors implement the Error interface which allows you to display them as a string if you can't handle them based upon their type. It's not unlike exception handling by exception type, although it's definitely more of a manual process because Go doesn't give you specialized syntax for it.
- flogic 13y agoI stand corrected. I'd still prefer normal exception handling but whatever.
- fleitz 13y agoYes! Most software I've seen can be dramatically improved by removing exception handlers. (And then fixing the underlying problems). On the server side I prefer one exception handler which is the OS, it will kill your process, release and free all memory, file handlers, etc. Then I wrap it in a bash script / other monitoring tool, and have it immediately restart whenever it fails. As for the server itself I'll also add an automatic reboot to the server to ensure nothing out of the ordinary persists for very long (like that PATH modification that someone forgot to add in the appropriate place to get set on restart). If it's virtualized I prefer to destroy the entire server if possible.
- dtwwtd 13y agoAre the servers you're building serving concurrent clients? An exception could take out multiple in-flight requests. (not against your idea, just curious how you handle it)
- falcolas 13y agoOne advantage of forking servers; kill and reboot the parent, and you don't loose in-flight connections. That said, I do this as well, even the best behaved daemon can get... funky... after a few months. Planned outages for a daemon restart are ok in my experience, particularly if you can fail over to other nodes as part of a rolling restart. Of course, this refers to planned restarts, though forking servers helps with unplanned exceptions as well.
- pof33 13y agoThis is 2013 calling. Init daemons now handle all this for you. Your silly experience building servers out of matchsticks is hereby considered irrelevant.
- lmm 13y agoDon't you find performance suffers? AIUI this approach means you can only handle as many concurrent requests as you have processes, and the OS scheduler has less information to work with than if you were using threads.
- blahbl4hblahtoo 13y agoYou are totally correct about the engineering that goes into complex software development being the cause of the stability this guy mentions.
- yew 13y agoThe problem with exceptions (in certain contexts, of course) is precisely these assumptions. Changes in low-level routines can change what your function is able to handle. Sometimes "just let it crash" isn't an option, period. Often, the exception hierarchy doesn't expose enough information to handle an exception without outside context (vis Python's OSError). In many cases, exceptions are superior to returning error codes because of ease of use and debugging. In many other cases, returning error codes (or something like Java's checked exceptions, although those have some rough edges) are superior. I've yet to see a universally applicable error handling model, and I suspect I never will. (By the way, I believe the ability to throw arbitrary values in C++ is, somewhat loosely, related to something called 'foreign exceptions'. The Itanium C++ ABI on exception handling is an interesting read here. C++ implementations support throwing things into and out of non-C++ code, and simply don't limit what C++ code can throw under normal circumstances. Though perhaps they should have.)
- betterunix 13y ago"I've yet to see a universally applicable error handling model, and I suspect I never will." I'll bite: http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html http://www.gigamonkeys.com/book/beyond-exception-handling-co...
- yew 13y agoCommon Lisp's condition/restart system is very nice. It solves the context problem, which (at least in my experience) is the biggest problem with exceptions in day-to-day programming. CL's dedication to the debugger also greatly reduces the downsides of not explicitly handling errors. You get the ability to notify the user of problems that they might be able to resolve manually for free. The lack of function signature information is still a problem, though. Sometimes you really do need to know what exceptions might be thrown. There's also the issue of overhead . . . I'm happy to say that exceptions are nice to have, but the idea that they're superior to returning error codes at all times in all places (especially in a language that supports returning multiple values) is more than a little silly.
- mtdewcmu 13y agoIn a lot of ways, comparing syscalls like stat and Java methods is apples and oranges. System calls are at the very bottom of the stack, where userspace interacts directly with kernelspace, so they must return all error information, otherwise there would be no other way to get it from userspace. So it's not really examplary of C language style that syscalls behave this way. Syscalls are not designed to obscure what's really going on by putting a friendlier interface on it; at some level you need the truth. Having said that, though, there are aspects of C that could lend it to writing robust code. The concepts are concrete, for one thing. Despite the fact that high-level languages are designed to be easier to understand, I suspect that concrete concepts are inherently easier to grasp than abstract ones (and it doesn't hurt that concrete concepts don't leak). What C contributes is a concise and eloquent syntax for telling the machine what to do, with little waste. Some people argue that it's dangerous to control the machine directly without the compiler being able to watch over you; but not having extra annotations for strong typing and such makes it easy to see what's happening, and I think that concision and transparency is key to writing robust code.
- penguindev 13y agoIt's no longer even true that the kernel returns all error context; for example selinux is supposedly a nightmare to figure out "why was this denied?".
- mtdewcmu 13y agoMaybe that's because SELinux was handled by the NSA, and they're not so thorough?
- yason 13y agoI disagree with being polished. Newer C programs can be robust as well. In fact, I write C programs both for fun and at work and so do a lot of people I know, and these programs still end up being much more robust than I would even expect myself. And when you run them continuously, you will find some bugs in the program but the next bug will appear much later until you don't remember when was the last time a bug caused a failure. There's something about the robustness you're forced to implement. I never trust scripting languages like that, not mine nor others', as much as solid C. I've seen it way too many times that when a C program is well-written solid code, it keeps on doing what it does and doesn't give surprises. I've had many Python scripts running for years and I always need to fix something little there every year. Maybe something has changed in some library, or I bump into a new failure mode, or something. This rarely happens with a C program once it has "settled".
- mtdewcmu 13y agoI worked with PHP for a while and I found it to behave like you describe: I rarely found bugs in code once it reached a stable state. I haven't used Python (I might be the only one), so I can only speculate on whether it's truly more bug-prone than PHP. But one possible reason could be the relatively straightforward behavior of PHP, and C code. (My experience is with non-OO PHP, mainly from hacking Drupal modules, which might make a difference). C and (older) PHP have a small number of data types and few or no hidden behaviors. Neither is OO. I wonder if OO might contribute to long-term instability, because since OO languages are rich in types, they tend to let the language make inferences and allow types to alter the meaning of syntax. For predictable code, you probably want a language that isn't too smart and always gives the same meaning to the same code.
- pof33 13y agoWow, the first comment is some posturing, opinionated douche spewing dogma and self-importance. On HN no less. How novel.
- mannykannot 13y agoIf this were valid, there would be no need for an exception type hierarchy at all. In reality, we have different exception types (and error codes) because there are circumstances when the type of exception determines how it may be handled. Therefore, a professional programmer needs to be able to find out what exceptions could occur in any given call (which is not the same as saying that she needs to consider each one individually after every call - this is where your 'what can be handled here?' principle has some use.) Terminate-on-exception might just be a workable policy for a simple or unimportant application, but it is not how you create a robust software infrastructure or safety-critical software. The original article describes a style of programming in which his programs are built directly on OS services without middleware, and in which the programmer is aware at all times of what might go wrong. I think it is this, rather than the distinction between error codes and exceptions, that contributes to the reliability of the programs he is writing about. Creating a new exception type that could propagate out of the abstraction you are working on is a serious matter, so it would not be a bad thing if it were a non-trivial thing to do. Unfortunately, the attempts to make this so in Java and C++ put most of the burden on the users of the abstraction instead, and this seems to be built in to the nature of this problem.
- ithkuil 13y agoOr, writing C software with an acceptable quality requires you to put relatively big effort in developing, testing and polishing; the result of which is usually higher quality than what you get in a language where things are easier to write and you don't feel you have to be careful.
- rrrrtttt 13y agoExceptions are kind of like gotos, but worse, because the jump target is decided at runtime based on the state of the call stack and therefore cannot be determined just by looking at the code. So they're more like comefroms [0] than gotos. [0] http://en.wikipedia.org/wiki/COMEFROM http://en.wikipedia.org/wiki/COMEFROM
- kazagistar 13y agoRight, which is why they are reserved for situations where you are not trying to "jump somewhere", but just want to give up and crash, because further execution in the current context has failed. If some external calling context really does not want to crash, the implementation of failing function doesn't and shouldn't care.
- deleted 13y ago[deleted]
- zaphar 13y agoThat might be what they are intended to be reserved for but the reality of what they get used for is much more of the "Jump Somewhere" sort.
- bashinator 13y agoReally? I had it drilled into me from day 3 or so, that "EXCEPTIONS ARE NOT FLOW CONTROL!"
- zaphar 13y agoAfter more than a decade of seeing exceptions in use it has become very apparent that no matter what they might have been told when they learned about exceptions most people use them for flow control. Mostly because they are a way of doing flow control.
- dragonwriter 13y ago> So they're more like comefroms than gotos. Except, not, because the jump source for a particular exception handler is no more (and no less) determinable in advance than the jump target of an exception raised at a particular point. They are very much unlike either gotos or comefroms. If you are going to criticize exceptions, do it directly, rather than by asserting that they are like something that they are completely unlike.
- jimmaswell 13y agoI disagree with 4. How else would you recommend structuring this: try{ lst.Where(i => i.ID == itm.ID).First().Amount += itm.Amount } catch (InvalidOperationException) { lst.Add(itm); } I could check if the list contains it first, but this is more convenient. This is just one small example of a lot of cases where I've used typed exceptions successfully - in some cases I have multiple catches that do different things, eg catch(InvalidOperationException){...}catch(ArgumentNullException){...} this saves a lot of lines of pre-checking and has a clear control flow.