3 ms·
Raising an error is an intentionally defined effect. Doing something under-defined, by accident, that you'll at some point break because you weren't thinking o
by MaulingMonkey 9y ago
Raising an error is an intentionally defined effect.
Doing something under-defined, by accident, that you'll at some point break because you weren't thinking of that specific edge case when e.g. refactoring, for someone else who didn't actually intend to exercise that edge case but never realized they were doing so as they never received an error... well, not my cup of tea.
- candiodari 9y agoAnd causing errors for everything will cause your program to bug out in perfectly valid cases. Defensive versus total programming. Either have their place, but ... Thing is most programmers vastly overestimate the case for defensive programming. In practice, most programs need to run. So for instance, the control loop for a nuclear reactor is not defensively programmed. It never, ever, ever gets to yield an error. Why ? Because the system is not "fail-safe". If the program ever were to say "this doesn't make sense, I'm quitting (or otherwise doing nothing)", there is nothing guaranteeing the system is in a safe state, and so it may melt down. A bug that fails to notice a critical condition, and ignores it, on the other hand leaves the system under the control of the operator, and nothing (should) go wrong (meaning the second level checks should catch the problem) The general guideline is defensive programming when starting up, with a force switch to override it (so the system can be (re)started if operators decide that's the best outcome). Once the program is running, total programming should be your go-to solution. When in doubt, do the best you can and continue on to the next request. Since I've been programming million qps+ systems, I've learned that there's even nothing wrong with almost totally ignoring errors. You'd be surprised how well the following works: before handling a request, note down the time (using the cheap instruction), then every time you leave your method, check the time. If it's over a timeout, increase a counter, log if not logged in the past 10s, and send an empty reply. This makes the developer's job harder, but makes everybody else a lot happier.
- MaulingMonkey 9y ago> And causing errors for everything will cause your program to bug out in perfectly valid cases. Erroring out is admittedly not a panacea to every situation, but the user-facing end of a command line interface is pretty low hanging fruit and a pretty obvious place to error out. The nuclear reactor equivalent wouldn't be the low level control loop, it'd be how the display panels react to operator inputs and sensors - the red lights they turn on, the klaxons they sound, the systems they engage to attempt to put the reactor into a safe state when it thinks it's getting ready to melt down. And even for that low level control loop, you'd better believe they're thinking very very hard about, and once again intentionally define, how they handle any edge cases they can think of, not just throwing caution to the wind and letting the program's behavior fall where it may. Even for a rocket, sometimes the best answer is "do nothing - and let the range safety systems blow the damn thing up if that wasn't the right answer." > Since I've been programming million qps+ systems, I've learned that there's even nothing wrong with almost totally ignoring errors. You'd be surprised how well the following works: before handling a request, note down the time (using the cheap instruction), then every time you leave your method, check the time. If it's over a timeout, increase a counter, log if not logged in the past 10s, and send an empty reply. This makes the developer's job harder, but makes everybody else a lot happier. Totally ignoring errors leaves you with crashy garbage that does not accomplish your goal of high uptime/availability/not corrupting data in C++ - I'd wager you're dealing with safer languages to type that with a straight face! Even there, though, you probably write code to at least handle null references and other edge cases. You might not trigger fatal assertion dialogs, you might not log to some fancy telemetry system, you might not even log at all - but something to handle error conditions. And if you're writing that code anyways, you might as well write some logs/telemetry as well to make your devs lives easier. You don't have to force it into the face of your end users, you don't have to make the whole thing explode in the face of adversity, but some opt-in dev tools for yourself tends to be nice. A fun trick from gamedev: "Assertion" macros that force you to include an error handling statement (often a simple "return;"). Devs get to check out if that null pointer passed to your leaderboards is a systemic bug that broke all leaderboards, gamers get to keep their quest progress instead of the entire game crashing because a server being down or giving a malformed response means the game doesn't know how to handle updating a single leaderboard occasionally. Best of both worlds. Be greedy - you can have happy users and devs.
- candiodari 9y ago> Totally ignoring errors leaves you with crashy garbage that does not accomplish your goal of high uptime/availability/not corrupting data in C++ - I'd wager you're dealing with safer languages to type that with a straight face! Even there, though, you probably write code to at least handle null references and other edge cases. You might not trigger fatal assertion dialogs, you might not log to some fancy telemetry system, you might not even log at all - but something to handle error conditions. That is exactly what I mean. Defensive programming is erroring out and refusing to do anything from that point forward (think of it like an uncaught exception with less information). "Total" programming is doing the best you can, given what's available. Generally if I am unable to do something, or look something up, I'll return a partially filled out request, log it (logs are rate-limited VERY early in the logging process), and increase a counter created for that particular case. (I do use C++ often, but we use variable guards to guard against null pointers, use-after-free, and sharing (unless explicit)). > A fun trick from gamedev: "Assertion" macros that force you to include an error handling statement Link ? I'm interested.
- jsjohnst 9y ago> Defensive programming is erroring out and refusing to do anything from that point forward (think of it like an uncaught exception with less information) I’m not sure where you got that impression, but it’s not the generally accepted definition. Defensive programming does not imply crash/abort/exit, it just simply implies a style of programming where you don’t trust “things will always be ok”. That could involve checking every input for correctness, checking every function output, handling all exceptions, etc. Yes, a defensive method could throw an exception on invalid input, but if the entire program is written defensively, then it should be handled and potentially continued.
- MaulingMonkey 9y ago> Link ? I'm interested. The examples I'm thinking of are all closed source, but here's a trivial implementation on the simpler end of the scale in my favorite style/variation of the idea: #include <iostream> #include <sstream> namespace xyz { constexpr bool logFails = true; } #define XYZ_CHECK_MSG(cond, msg, expr) if (!(cond)) { \ if (::xyz::logFails) { ::std::cerr << msg << ::std::endl; } \ expr; \ } else ((void)0) /* ; */ #define XYZ_CHECK(cond, expr) XYZ_CHECK_MSG(cond, "XYZ_CHECK(" #cond ", ...) failed", expr); void increment (long * i) { XYZ_CHECK(i, return); // nonfatal ++*i; } int main(int argc, char ** argv) { XYZ_CHECK_MSG(argc > 1, "Usage: " << argv[0] << " [number] [number]", std::exit(1)); // super fatal for (int iArg = 1; iArg < argc; ++iArg) { const char * const arg = argv[argc]; char * argEnd = nullptr; long n = std::strtol(argv[iArg], &argEnd, 10); // doesn't even end the function - "continue" could be replaced with "n=0" as well! XYZ_CHECK_MSG(arg != argEnd && *argEnd == '\0', "Argument " << iArg << " was not a number", continue); increment(&n); std::cout << n << "\n"; } } Technically, if you don't want an error handling case, you don't have to provide one, but that's a bit explicit - you won't forget to at least think about it unless you really work at establishing such a bad habit: XYZ_CHECK_MSG(condition, "Something bad happened, but let's just continue on our way.", ); More complicated versions of this might define additional macro params to: 1) Define a name and default severity - such that you can squelch false positives that snuck into the build before you can create and deploy a new one, or enable slower debug checks that might be skipped by default. 2) Configure where the logs go or what categories the messages will be filtered into. 3) Tag JIRA tasks or bug numbers to automatically reopen, or specific authors to alert. Although these can also be handled in a more orthogonal manner.
- DonHopkins 9y agoA bare "-" argument has a specific meaning in other unix programs, like "read from stdin". Giving it a different meaning like "no meaning so ignored" is inconsistent and misleading. If "-Y" has no meaning but is not ignored because it generates an error message, then if "-" has no meaning it should also generate the identical error message.
- jsjohnst 9y ago> I've learned that there's even nothing wrong with almost totally ignoring errors. I sincerely feel bad for developers who work with people who have attitudes like the above. Detecting production issues is miserable with code like that, let alone debugging them.