4 ms·
> 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, b
by 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.