5 ms·
This post is close to 20 years old but it's still how I handle errors: https://www.joelonsoftware.com/2003/10/13/13/ https://www.joelonsoftware.com/2003/10/13/1
by jameshush 4y ago
This post is close to 20 years old but it's still how I handle errors: https://www.joelonsoftware.com/2003/10/13/13/ https://www.joelonsoftware.com/2003/10/13/13/
From the article:
People have asked why I don’t like programming with
exceptions. In both Java and C++, my policy is:
1. Never throw an exception of my own
2. Always catch any possible exception that might be thrown by a library I’m using on the same line as it is thrown and deal with it immediately.
I've been coding this way for ~10 years now and it's made things a lot easier for me. If the user should know about the error, let them know about it, if it should be hidden from the user, I'll track it in something like Sentry or Datadog and send out an alert for whoever's on call.
- Aeolun 4y agoHow do you deal with something 15 layers deep that has an error? Bubble up the error through all those function returns (e.g. Go style)?
- Cthulhu_ 4y agoIf needs be. But ideally you get to a point where an error turns into a business rule. SQL "no rows found" error? Becomes a business "record not found" error, which is turned into a HTTP 404 on an HTTP API edge.
- jstimpfle 4y agoDon't have call stacks 15 layers deep. It's a problem in itself when the only choice you have is either ignoring the error or cancelling 15 layers of god-knows-what-code-must-still-run.
- simiones 4y ago> catch any possible exception that might be thrown by a library I’m using on the same line as it is thrown and deal with it immediately. What does "deal with it immediately" mean, that exceptions don't already do? Edit: to explain a little more: when an error is returned from a procedure, control flow can do one of exactly two things: 1. Handle it in some specific way (retry the operation that failed, proceed with a default value for an optional operation, try to modify input parameters and run again, ignore completely, etc) 2. Return the error to an upper-level caller (calling function, OS). You may of course add some optional extras (log the error, clean up any resources), but the above are the only 2 options for how control flow can continue. Now, if you can do 1: perfect, do it, that is the best case scenario. But, there will always be cases where you can't handle this error, and your only option is 2. Now, 2 can be achieved by many mechanisms: exit(ERR_CODE) to return to OS, automatic exception propagation, manual error return value propagation, set some error flag and return, etc. But they are all ultimately equivalent, and each have their pros and cons.
- jstimpfle 4y agoExceptions don't deal with something, they cancel everything else that was scheduled to run after. If you catch an exception immediately (in the calling function) then that's immediate handling. But then there wasn't really a point in using an exception vs returning an error code. Exceptions mainly allow you to be lazy, and work well with systems that have some introspection how to cleanup themselves: Garbage collection, RAII, OS processes... I'm not convinced that relying on these systems scales well to complex applications. And maybe I'm doing something wrong, but I don't think manual error handling is a big hassle if you put some thought in structuring the system.
- simiones 4y agoMy question was "what does it mean to to you (or to Joel Spolsky, since it's his words originally)" to "deal with an error immediately"? Do you see some third option in addition to my 1 and 2? Otherwise, whether you write: { auto x = new int[100]; int err = foo(x); if(err) { delete[] x; return err; } //do something with x delete[] x; return 0; } or you write { auto x = std::unique_ptr<int>(new int[100]); foo(x); //do something with x } I don't see any significant difference. The exception case is shorter (less code to review), while the manual error return code is more explicit (easier to review the code).
- jstimpfle 4y ago> I don't see any significant difference. Because this is a toy example. In a real system, you avoid allocating / deallocating the buffers right before and after you work on them, to begin with.
- simiones 4y agoYou are not answering my question. The fact that x is a buffer is irrelevant. Here, I'll rephrase: Otherwise, whether you write: { auto x = fopen("someFile"); int err = foo(x); if(err) { fclose(x); return err; } //do something with x fclose(x); return 0; } or you write { auto x = std::ifstream("someFile.txt"); foo(x); //do something with x } What is the fundamental difference?
- dangerface 4y agoI have to say I hate that. I do not miss the "Is this actually failing?" debate I get every time I write C. Joel complains about exceptions being invisible but how is returning errors any better? If a function returns 0 has it failed? If it has failed why? Has it lost connection? No results? A generic no? If another function from the same library returns a 0 is that also an error or is error reporting inconsistent? It's impossible to know what is being communicated to the point I don't even know its an error.
- mekster 4y agoSounds quite ineffective. Just throw an appropriate exception type and deal with them in a single spot. What happens if you forget to catch an exception? System default error measage printed out?