115

I am confused because in quite a few places I've already read that the so-called 'boneheaded' exceptions (ones that result from bugs in code) are not supposed to be caught. Instead, they must be allowed to crash the application:

At least two of the three above people are established authorities.

I am surprised. Especially for some (important!) use cases, like server side code, I simply can't see why is catching such an exception suboptimal and why the application must be allowed to crash.

As far as I'm aware, the typical solution in such a case is to catch the exception, return HTTP 500 to the client, have an automatic system that sends an emergency e-mail to the development team so that they can fix the problem ASAP - but do not crash the application (one request must fail, there's nothing we can do here, but why take the whole service down and make everyone else unable to use our website? Downtime is costly!). Am I incorrect?

Why am I asking - I'm perpetually trying to finish a hobby project, which is a browser based game in .net core. As far as I'm aware, in many cases the framework does for me out of the box the precise thing Eric Lippert and Stephen Cleary are recommending against! - that is, if handling a request throws, the framework automatically catches the exception and prevents the server from crashing. In a few places, however, the framework does not do this. In such places, I am wrapping my own code with try {...} catch {...} to catch all possible 'boneheaded' exceptions.

One of such places, AFAIK, is background tasks. For example, I am now implementing a background ban clearing service that is supposed to clear all expired temporary bans every few minutes. Here, I'm even using a few layers of all-catching try blocks:

try // prevent server from crashing if boneheaded exception occurs here
{
    var expiredBans = GetExpiredBans();
    foreach(var ban in expiredBans)
    {
        try // If removing one ban fails, eg because of a boneheaded problem, 
        {   // still try to remove other bans
            RemoveBan(ban);
        }
        catch
        {

        }
    }
}
catch
{

}

(Yes, my catch blocks are empty right now - I am aware that ignoring these exceptions is unacceptable, adding some logging is perpetually on my TODO list)

Having read the articles I linked to above, I can no longer continue doing this without some serious doubt... Am I not shooting myself in the foot? Why / Why not?

If and why should boneheaded exceptions never be caught?

30
  • 65
    This is a bit OT, but I strongly suggest to fix all empty catch blocks ASAP. Any such empty block can cost your hours when something goes wrong and you have no clue what and where. Just add any logging now, e.g., write a trivial class with a static method printing somewhere.... switching to something better later is trivial.
    – maaartinus
    Commented Jan 5, 2020 at 13:44
  • 31
    "logging is perpetually on my TODO list" is precisely why you should never catch boneheaded exceptions. I would also argue to never put // TODO comments in either. You never actually get back to properly handling either. (I was just recently burned wasting about 2 days in legacy code that swallowed a NPE in an empty catch block.)
    – Dave Rager
    Commented Jan 6, 2020 at 0:15
  • 10
    Serious question: Why isn't the try-catch inside GetExpiredBans and RemoveBan? The assumption here seems to be "these can fail arbitrarily at any time and we can always ignore that failure". OK, if that's the case then put the try-catch inside them and then the caller doesn't need them. Why did you decide to put the try-catch in the caller, rather than the callee? Commented Jan 6, 2020 at 20:55
  • 13
    Second serious question: How often do those catch blocks execute? That is, how often are you genuinely catching a "boneheaded" exception such as a null dereference? If the answer is "zero times per year" then the catch blocks are unnecessary. If the answer is not zero then fix all the bugs until the answer is zero, and then the answer will be zero. Commented Jan 6, 2020 at 20:59
  • 6
    I feel it important to point out something I haven't seen (m)any answers or comments on this page address. Yes, you should allow boneheaded errors to just happen, because hiding them means hiding a bug in your code. However, the reason they are called "boneheaded" is because they are your fault and should never have happened in the first place. If you find that a boneheaded error gets thrown by your server, don't just wrap it in a try/catch and attempt to "fail gracefully". Rewrite your code so it would never get thrown in the first place.
    – Abion47
    Commented Jan 7, 2020 at 0:45

17 Answers 17

246

Silent But Deadly

When writing enterprise software, you will eventually learn an essential truth: the worst bug in the world is not one that causes your program to crash. The worst bug in the world is one which causes your program to silently produce a wrong answer that goes unnoticed but eventually produces a massive negative effect (with severe financial implications for your employer). Thus, error messages and crashes are A Good ThingTM, because they indicate that your program detected a problem.

Amazing Grace

Now, this seems to conflict with another enterprise virtue, which is "degrade gracefully". Blowing up and not returning any response at all hardly looks like "graceful degradation". And this is why many folks will try very hard to return some response, if they can. Indeed, this is why many frameworks, like Spring, will catch all top-level exceptions and wrap them with a 500 response, as you describe. In general, I think this is OK. After all, most exceptions don't really require a restart of the entire app server if you can just kill/restart a server thread. A sane framework will be careful to not catch Java Errors, like OutOfMemory, for obvious reasons.

But there is one more point to consider: once you get beyond a single server, you will likely have a load balancer in front of your service. And when the LB times out or gets a closed connection, it will generally return a 500 to its client. Thus, the LB will often transform your "server crash" into a client 5xx automatically! Best of both worlds.

Worst Case

In your scenario, what is the worst that can happen if you don't catch the exceptions? Your answer: "Well, my game server dies, and nobody can play!!!" But that's not the worst case. The worst case is, everyone is playing your game, but griefers are ruining it. Players file a bug report and tell you that bans aren't working, but you look at the logs and everything looks fine. Or, legitimate players are getting banned by griefers, and instead of being able to rejoin in a timely manner, the bans are lasting indefinitely, because your server happily ignores failures. The worst thing isn't your game crashing. It's your player trust crashing. Good luck trying to reset that.

33
  • 107
    Excellent answer. With a background in safety critical systems I can add one scenario to your arguments: even in e.g. a plane it is better to crash the computer, leaving the pilot with NO information, than taking the risk of showing WRONG information. The problem has to be solved on an completely different level, e.g. by redundancy. Commented Jan 5, 2020 at 10:15
  • 25
    It depends on the situation. I'm thinking of the first launch of the Ariane V. An unchecked exception took out the rocket when the offending problem would have had absolutely no effect on the mission. When there's real hardware being controlled barfing is sometimes the wrong answer. (The calculation that caused the /0 error was actually only of value while sitting on the pad, it became moot in flight.) Commented Jan 6, 2020 at 3:43
  • 7
    @HugoZink usually a good idea, but depends on the application and the problem. If it's something that affects a single request, log it, alert sysadmins, and return an error value to the client. If it's something that causes the entire system to become unreliable (memory error, core configuration corruption, etc.) take down the system. There's no one-size-fits-all solution.
    – jwenting
    Commented Jan 6, 2020 at 10:11
  • 7
    The second worst case is that your servers are always down and so you lose player trust. Which is worse: having griefers run amok for a day, or having nobody play for a day? That depends on how much persistent state your game has, probably. If it's a Quake-style FPS with nothing saved between rounds, it'll be "hey remember that time half the other team could fly?". If it's a Minecraft-style building game, it'll be "hey remember that time we lost three months of work?" Commented Jan 6, 2020 at 11:36
  • 7
    I can think of much worse cases than that, involving lawsuits and criminal charges.
    – OrangeDog
    Commented Jan 6, 2020 at 14:16
59

Exceptions should be allowed to crash the system if the system has been left in an unrecoverable undefined state. If you can't put the system back in a defined state that ensures data integrity and security then you crash so the system can be rebooted into that defined state.

Whenever you catch an exception you're taking responsibility for doing all that recovery yourself. You should only leave your catch empty when you are absolutely sure the system is still in that defined state and nothing needs to be done. This happens when the calling code has a better idea of the system state than the code that threw the exception.

That's all there is to it. Calling an exception boneheaded is just being pointlessly rude. In some contexts you simply don't know what will happen to system state when you fail, so you throw and let the calling code figure it out. If the calling code can't figure it out then you crash and let the operator figure it out.

Now if you somehow know that failing won't put the system into a bad state then you don't need to throw in the first place. Throwing was never meant to be the only way to fail to do what you were asked to do. You can return -1, or the empty string, or null (ick), or a null object, or an empty collection, or a "maybe" monad, or print "Your princess is in another castle", or just do nothing, quietly. Yeah, I know. But sometimes that's ok. Why? Because at times failing to do what you're told is correct, expected, and not interesting.

9
  • 15
    It is also easier to fix the problem (bug) that caused the exception when the crash makes you aware of it. If you silently catch the exception and keep going, you can end up masking an issue that will become a bigger problem later. Commented Jan 4, 2020 at 20:20
  • 1
    What you said seems to make sense, but... Still, the result of following this advice in server code is that instead of returning HTTP 500 and continuing to server other requests we take the whole website down! AFAIK webapps typically opt for the first behavior, not the second? (Unless typically half of webapps' code is error recovery code?)
    – gaazkam
    Commented Jan 4, 2020 at 20:51
  • 3
    @gaazkam not true. When a website sends you a 500 it's doing so because it has recovered and part of that recovery is letting you know that whatever work you thought you were going to get done is gone. If it hasn't recovered to a safe stable state the last thing it should do is tell you about it. Commented Jan 4, 2020 at 22:20
  • @candied_orange So, typically, is half of server side code error recovery code? Do backend authors try to anticipate every possible bug they might introduce and write code to recover from every possible bug to avoid downtime?
    – gaazkam
    Commented Jan 4, 2020 at 22:59
  • 19
    It is not true that calling boneheaded exceptions is pointlessly rude. I called them boneheaded exceptions deliberately; the comical rudeness was the point, so that the term would be memorable. You might not have internalized what is boneheaded about a boneheaded exception. A boneheaded exception is always avoidable, and almost always indicates that the programmer forgot to handle a valid value or code path. That obviously some of these defects are subtle and difficult to see, and that's why they persist in the wild, is part of the humour. Commented Jan 6, 2020 at 15:31
43

It doesn't really matter if it's a "boneheaded" exception (e.g. a Java unchecked exception) or not.

The only question to ask yourself is:

"Can the program sensibly continue?"

A "server" is a program that processes messages. Typically, each message is mostly independent from the others. That is: if there is an error processing on message, it makes sense to continue receiving and processing future messages.

This would apply to an HTTP server, a job server, etc.

Some errors may not be sensibly recoverable, like OutOfMemory. But if you can recover from an error (including possibly logging it some way), then you should.

11
  • 3
    I would say "sensibly do something about it" instead of "sensibly continue" - because there are sensible actions like "log the error, then exit (and probably get restarted by service supervision)" can be called "recovery", it is hardly "continuing", in casual or practical terms, or technical terms within the scope of the process' lifespan.
    – mtraceur
    Commented Jan 6, 2020 at 22:21
  • Boneheaded exceptions might cast doubt on the sensibility of continuing. I mean, if someone's getting exceptions from their own code not behaving as intended, it'd seem potentially hazardous to just keep calling that same code anyway in scenarios in which that code could, for example, corrupt a database of user accounts. For example, in the question, they try { RemoveBan(ban); } catch {}, which presumably modifies a database with user accounts; if that throws due to a programming logic error, then what exactly is that code doing, and can it be trusted to keep modifying the database?
    – Nat
    Commented Jan 6, 2020 at 22:21
  • @Nat, if other operations are dependent on RemoveBan() then they should not continue. Commented Jan 7, 2020 at 5:52
  • How do ou know the program can sensibly continue? If that's because you carefully evaluated that error source it no longer is a "boneheaded" exception. If it's something you did not plan, then your program is doing something you did not expect, and is now in an unexpected state. There is no telling what invariants no longer hold, what things may be in an inconsistent state and… how long it will corrupt data unnoticed if you leave it be instead of triggering a clean restart by service supervision.
    – spectras
    Commented Jan 8, 2020 at 0:03
  • 1
    @spectras, the layering you speak of can be as simple as a stack layer; i.e. a function call. There's no more reason to suspect ArrayIndexOutOfBoundsException is corrupting the entire process anymore than a process crashing corrupts the entire OS. (It might have side effects that do that, but that's uncommon.) Commented Jan 1, 2021 at 1:49
23

You are absolutely correct. An exception in server-side application code ("boneheaded" or not) should not crash a web server.

The confusion is because the articles are not clear about what it actually means to "catch" or "crash". If we followed advice never to catch "boneheaded exceptions", then a single application bug should bubble up and cause the whole operating system to crash. This is not what anybody wants!

Rather you should think of a system as multiple layers of isolation. An unexpected crash in one layer should not cause outer layers to crash. An operating system will isolate crashes in individual processes and will not let a crash in one process take down other processes or the whole OS. A web server will typically isolate crashes in single requests and not let them affect any other request.

An application may itself have multiple layers. E.g. an application supporting plug-ins might isolate crashes in individual plug-ins. If a plug-in crashes it is disabled, but the rest of the application continues.

But isolating crashes can only work if the units of isolation are independent. Web requests are independent from the perspective of the web server. A crash in one request will not cause any other process to fail or receive invalid input.

So the bottom line is that you shouldn't catch boneheaded exception in your own application code, you should let a higher level (the framework) catch them.

Catching a boneheaded exception in your own code does not really make any sense. A boneheaded exceptions means there is an unexpected bug in the section of code. In other words, the code does something wrong, but you don't know exactly what or why. At best nothing happens when you execute the code, at worst some state is corrupted which might cause other parts of the system to behave erroneously and corrupt more state.

So if the code detects that there is a bug in the RemoveBan method... why do you want to execute it again? I can't imagine any scenario where you would want to repeatedly execute some code which you know does something wrong.

2
  • 1
    "If we followed to never catch "boneheaded exceptions", then a single application bug should bubble up and cause the whole operating system to crash." Sorry, that's just plain wrong. An exception cannot bubble out of its process. To the kernel, a throw, stack-unwind, catch is just a bunch of machine code like the rest it loaded from the binary. And the process's internal state, that which can be inconsistent due to an error, is none of the kernel's concern. To the kernel it's just some memory pages that are allocated to the process. That separation is the main reason we use virtual memory. Commented Jan 6, 2020 at 20:36
  • 5
    @cmaster-reinstatemonica Obviosuly a .net exception can never escape the CLR. My point is that even if the web server was written in .net, you wouldn't let an exception in a request handler take down the whole server. You do want to catch all exceptions, even boneheaded ones. You just have to do it at the right level.
    – JacquesB
    Commented Jan 7, 2020 at 13:27
18

You learned an important thing: Whenever you read a rule on the internet that must absolutely be followed, you must start thinking about it and decide for yourself whether you should follow the rule in your specific case or not. And you should also think about whether the rule as you understand it is a good rule or not.

Here some rules that you can think about: A server should never crash unless absolutely necessary. A client on the other hand is absolutely allowed to crash if you make sure there is no substantial data loss, users will often not even take notice.

So for a client, crashing is not at all a bad response to a boneheaded exception. For a server, it is a much worse response. Still, you have to weigh up: You have an exception that is totally unexpected to you. You have no idea how to handle it properly (reporting an exception isn't handling it). How much damage could be done by continuing when you know something has gone badly wrong? So do you want to continue operations, with unknown and potentially unlimited damages, do you want to crash the server, causing some damage, or do you want to restart the server (not quite crashing, but restarting it as gentle as possible).

6
  • 1
    Note that even on a server, "you the generic developer" should not normally be catching these exceptions; your framework should do it for you. Commented Jan 5, 2020 at 4:41
  • 4
    I'm not sure that I agree about clients being allowed to crash and users not noticing. If I recall correctly, app stores record the number of crashes for an app and will greatly drop your appearance ranking if your app is crashing. For gaming, crashing is fairly unacceptable, even more-so for anything involving multiplayer/live content. On the other hand, a server will likely have a monitoring service that will at least reboot it after a crash, so things aren't as black and white as this makes it sound
    – Mars
    Commented Jan 6, 2020 at 1:48
  • 1
    @Mars a client literally crashing is a bad thing. Stopping, showing some monobutton error modal dialog and returning to the starting state (screen) after user input is not.
    – beppe9000
    Commented Jan 7, 2020 at 19:46
  • @beppe9000 That solves the issue for mobile where crash = lower ranking, but for user experience that doesn't change much. It would also require you to catch boneheaded exceptions in the first place
    – Mars
    Commented Jan 8, 2020 at 0:12
  • I just realized, "must absolutely be followed" should be "must 'absolutely' be followed". The former implies that yes, it absolutely must be followed (in which case it doesn't matter what your specific case is!), the latter implies that it's a questionable claim
    – Mars
    Commented Jan 8, 2020 at 0:15
10

I think that what you are failing to appreciate is that that the real-life consequences of errors can be much worse than simply having the server go down. Just for instance:

  • Erasing a database that is essential to the functioning of a company
  • Granting access to confidential information that shouldn't be granted
  • Approving a financial transaction that shouldn't be approved
  • Disapproving a financial transaction that should be approved
  • Destroying physical equipment under computer control
  • Killing a patient receiving radiation therapy under computer control
  • Causing flight control problems that result in the crash of two aircraft, killing 346 people

Ok, the list is a bit of a scarecrow. Ignoring exceptions is just one many possible errors that could have terrible consequences like these. But it is important to keep in mind that an exception being raised is telling you that one or more of your assumptions about the operation of the program is not valid, and you really need to think about the possible consequences of that.

Since you are writing a hobby game where none of these outcomes is remotely possible, you could just blow a raspberry and ignore this issue. However, if part of the reason you are writing this hobby project is to educate yourself, and to prepare yourself for writing software that other people will use, then you have to think carefully about this stuff, and practice writing code as if its correct functioning mattered. Yes, this is a lot more work.

No, you don't have to write handlers and recovery code for every conceivable exception. You can just deal with the ones that you think are important and know how to handle. But unless you are absolutely sure that an exception has no impact on the continuing correct functioning of the program, then it has to get passed on to the next higher level of control. That gives it the chance to recover or to abort the program and prevent incorrect functioning.

1
  • Or in Windows, having a user click OK to illegally pass information that Microsoft promises they won’t use.
    – WGroleau
    Commented Jan 5, 2020 at 21:52
4

I am surprised. Especially for some (important!) use cases, like server-side code, I simply can't see why is catching such an exception suboptimal and why the application must be allowed to crash.

There's nothing wrong with a crash, in fact, it can be very helpful to have an application crash early. @PaulDrappers answer of 'Can the program sensibly continue?' is really the key way of looking at it.

For example, if my application has been incorrectly configured - then I want it crashing immediately on startup. By quickly and obviously failing, this allows developers/DevOps to quickly identify and rectify the problem. The alternative, returning 500 errors, is less obvious and if intermittent may even pass health checks until a customer complaints.

On the other hand, if I get a sporadic error on a single request, I will typically 'crash' that single request (e.g. return 500), but otherwise, let the application continue running. After all, a single DB timeout shouldn't typically crash a website.

Though this is dependant on the application - if it's processing a single task at a time in a fault-tolerant pipeline (e.g. some kind of job or message processor), letting the application die might be appropriate (e.g. an orchestrator might have a failure policy we want to follow).

Whatever the situation I will try to log every error regardless if it's handled or allowed to bubble up.

1
  • 1
    Touché. Also a fun fact: your server will occasionally crash anyway, in unplanned ways :) And everything else as well.
    – kubanczyk
    Commented Jan 5, 2020 at 16:55
3

Your example demonstrates exactly why you shouldn't catch 'all possible' exceptions.

If your GetExpiredBans call fails, your code simply carries on as if it had been sucessful. The unbanner server is up and running and looks good, but actually it's not working at all.

Now if you know that RemoveBan occasionally fails due to a network problem for example, then you could catch that specific exception and implement a retry, or skip that player and move to the next. It's an understood problem and you know the desired behaviour.

If your code throws an exception you werent expecting then it's best to stop and have a human look at it.

Now you bring up the case of a HTTP server returning 500s instead of crashing.

Here the server is running an external program, ie your webpage. It's not the webserver which has a bug, its someone elses code. You wouldnt expect DOS to crash if you ran a buggy program, returning an error code and moving on is the same as that page crashing.

10
  • 3
    I'd venture that a failing minor functionality like autobanning users isn't a serious enough problem to crash the server, though it is serious enough to send an email alert (or possibly SMS or Twitter message) to a sysadmin to come take a look.
    – jwenting
    Commented Jan 6, 2020 at 10:13
  • @jwenting its your assumption about the consequences and reason of the unknown exception which are the root of the problem. Adding a sms message which sends in a tight loop would only exacerbate the issue
    – Ewan
    Commented Jan 6, 2020 at 10:21
  • 1
    In this particular case, the ideal outcome for an unexpected error is that the server continues running, the users don't get unbanned, and the administrator/developer gets notified that something went wrong so they can hopefully fix it before the next auto-unban cycle. Commented Jan 6, 2020 at 11:33
  • @user253751 sure, and the best way to achieve that is to crash the program and generate an alert through your monitoring solution. "server continues running" is not desirable if its just looping around hitting the same error each time
    – Ewan
    Commented Jan 6, 2020 at 11:45
  • @Ewan Why would that not be desirable? Commented Jan 6, 2020 at 11:47
3

Personally, I handle all exceptions.

And email them to myself.

It doesn't have to be email, you could just log them, but if you don't know what these errors are happening, how can you ever hope to correct them?

By all means, try to recover, or gloss over the error - to the user, but not to yourself.

For AJAX requests, this might mean sending a 500 response. For GUIs, the classic MicroSoft "something bad happened" (with its implied, and "we coders know what it is, but we are not going to tell you").

IMO, you need to inform your "user", whether human or software, but you also need to inform yourself, the development/maintenance team. These might be same action, or two different actions, but they both need to be informed.

3

There are actually two different types of exceptions that need to be handled differently.

First of all there are the "Non-bonehead" exceptions like "File not found". These exceptions are kind of expected, even if you check for the files existence beforehand it doesn't prove that it will be there when you go to use it.

These you typically catch as low/specific as possible, everyone understands this already because it's how everyone says to handle "Exceptions", so let's ignore it.

The other is unexpected exceptions. This covers both programming errors and some situational errors you aren't expecting to encounter (like the OS rips a disk out from under you). These are what you are calling "Bonehead" exceptions but they DO happen.

The important thing is that these are NOT ignored. If the only way to get a team to pay attention is to crash the app hard, then DO SO, but if you can get their attention another way, I recommend catching the most general "Exception" type just inside each primary thread loop and dealing with it in such a way as to get the teams attention, and then try to continue. You'd be surprised at how often this can keep your app running pretty much perfectly while you fix it. It's even allowed me to recover from an out of memory situation.

Not catching it is really not good compared to just handling it in a way that gets attention.

Also VERY important:

In Java (at least) by default when a thread throws an exception it is silently eaten, it doesn't crash your app or give you any indication that part of your program failed, but if that was a long-running thread, everything it powered is completely (invisibly) gone!

This can be fixed by installing a default exception handler, but be careful because without the handler or a try/catch, allowing such an exception to just silently kill a thread is the worst possible solution--it's possibly the most expensive thing you can do to another developer (or yourself!) I've spent weeks tracking down exceptions that were eaten by threads and empty catches!

2

There are two broad categories of exceptions: those that arise from the programmer's misuse of an API (e.g., invalid arguments) and those that arise from external causes (e.g., file not found). Exceptions from external causes should always be caught; exceptions from misuse of APIs should never be caught. This is because it's realistically achievable to avoid making these kinds of mistakes in the first place, and because these kinds of bugs usually manifest right away and should be identified and fixed, not handled in the code.

Distinguishing these categories of exceptions is much easier when you have language support (i.e., checked vs. unchecked exceptions) or when working with APIs that clearly distinguish them (i.e., different exception base classes.) Avoiding API misuse is also much easier in languages and IDEs that provide instant access to the complete documentation (e.g., Java/Javadoc in IntelliJ IDEA.) It's considerably more difficult in languages and IDEs that only provide instant access to a severely limited summary of the documentation (e.g., C#/Visual Studio.) In my experience, good IDE support for instant access to complete documentation vastly reduces the number of API misuse bugs that are ever introduced in the first place.

TL;DR:

  1. Constantly refer to the documentation to avoid misusing an API. Corollary: avoid using APIs that are inadequately documented.

  2. Do not catch exceptions arising from misuse of APIs. Corollary: avoid using APIs that do not clearly distinguish between misuse and external errors.

5
  • 1
    Is out-of-memory external or misuse? That depends. Is malformed data external or misuse? Probably the former, but the latter can be true. Commented Jan 6, 2020 at 19:43
  • @Deduplicator: Yup. In the real world, many programs need to deal with data from potentially unreliable or untrustworthy sources. Sometimes sanitizing data before processing makes sense, but in some cases validating data without processing it would require essentially as much work as processing the data while checking for validity throughout the process, and sanitizing valid data before processing would represent a wasted effort.
    – supercat
    Commented Jan 6, 2020 at 22:01
  • @Deduplicator The API designer should answer those questions and distinguish the exceptions accordingly. Commented Jan 6, 2020 at 23:08
  • @user560822 Unfortunately, the API-designer generally simply doesn't have the data to decide. Thus lower layers signaling errors up the chain instead of handling them. Commented Jan 6, 2020 at 23:13
  • @Deduplicator Sure they do. To use your example, the API designer decides and documents who should perform validation. If they let lower layers perform the validation, which they normally would, then the API would throw an exception indicating an external error. If the exception that may be thrown by the lower layer is the wrong type to indicate this, then a good API would translate it to the correct type (catch, wrap, and rethrow.) Commented Jan 6, 2020 at 23:24
1

An exception you don’t know how to handle at a particular place should not be handled there. It should be passed up the call stack until it can be handled properly. If it is not expected anywhere, there is at least some place where an operation can be canceled without causing a “wrong answer” or a crash.

1

Exceptions (Boneheaded or not) are Exceptional

If something is exceptional, by definition you have not considered it, nor know how to handle it.

Think of it like this. The sales assistant on the checkout of a store knows how to sell stuff, perform exchanges, etc..

They do not know how to handle Reporter Interview. The correct approach is to throw an exception to their Manager. Their manager may know how to handle this, and if so will then catch the exception and deal with the Reporter Interview. Otherwise what should the manager do?

Pass the exception up to their Manager. Eventually (if not dealt with earlier) this will reach the CEO/Business owner/Directors and have to be dealt with there. Even they may not know. In which case the exception escapes, and the Reporter Interview is not dealt with.

The worst scenario to occur is for the Reporter Interview to be dealt with by one of those people who does not have a clue how to handle it. Imagine the Reporter reporting on an poorly worded comment from the Sales Assistant or the Local Manager? Massive damage to the business reputationally, and quite possibly financially.

Business Software, be it in an office, or an online game is essentially just automating the process that would have otherwise had to be done manually by an actual worker. Ergo you want it to behave like a well mannered employee. It should only deal with the situations that it has a good well-defined response/activity for. Everything else should be handed over to its managers (usually its support team) for clarification (new code) and/or action (manual tasks).

8
  • But we do know that if something goes wrong with removing expired bans, it would be better to let the game continue but leave those players banned, than to crash the game. We also know that if this is likely to corrupt the database, it's better to crash the game than to corrupt the database. Commented Jan 6, 2020 at 11:34
  • hmmm... No. Knowing a desirable property is not the same as knowing the state and what can be done to progress. I know that it is desirable for my computer to do as i mean, that does not mean it won't do as I've written. It would be desirable to not kill the system just because a ban could not be lifted for some inane reason. But, exactly which inane reasons are those? OutOfMemory? ProgressiveLockFailure? DiskSpaceFull? DivideByZero? AssemblyNotFound? Any of those could prevent the ban from being lifted, exactly which of those leaves the system in a good to go state? Try a Task architecture.
    – Kain0_0
    Commented Jan 6, 2020 at 13:42
  • All of the above. If any of those are problems for the actual game, then the problem with the ban lifting will be logged, and then the same problem will crash the actual game (and be logged). Commented Jan 6, 2020 at 14:23
  • ... Either there is information in your scenario that you left out of your question such as logging your swallowed exceptions... or you have been exceptionally fortunate. None of those errors are guaranteed to be raised, consider AssemblyNotFound. A caching layer could preserve that knowledge and route around the issue. Presuming a ban removal on the list that fails before game code executes, that cache is primed. Later when you cleanup the expired ban list, and then at some point restart the server, all of the game servers start failing. Again which exception is boneheaded?
    – Kain0_0
    Commented Jan 6, 2020 at 22:36
  • Why wouldn't you get an AssemblyNotFound exception every time you called into the missing assembly? Commented Jan 7, 2020 at 11:09
1

To add an extra bit of flavor:

While you should allow your program to fail, it's not necessary that you make it fail catastrophically.

If your language allows you create your own exceptions, a great approach is to do something like this (Python):

# Exception is the base "user" exception class
class MyAppException(Exception): pass
class MySpecificError(MyAppException): pass
class MyOtherError(MyAppException): pass

...

if __name__ == '__main__':
    try:
        do_it()  # or whatever starts your app
    except MyAppException as e:
        # This catches any exceptions that *you* raised, and
        # is entirely the programmers fault for not catching before here!
        print('Well, we forgot to catch *this* exception:', e)
    except Exception as e:
        # This catches anything exceptions that may or may or may not
        # be within the programmers control, but include things like
        # KeyErrors or IndexErrors
        print('This was unexpected:', e)
    except:
        # Maybe not this one - it catches SystemExit & others. But, you *could*.
        print('Well, this was very unexpected')
    finally:
        # Do some cleanup stuff - close sockets, or whatever, and then quit
        exit(1)  # or some other failure code - it's up to you.

You can also re-raise your exceptions in Python if you'd like to really crash crash.

Taking this approach, you know:

  1. If you missed any exceptions that the developers should have known about
  2. If you missed any exceptions that you probably should have known about, but just didn't think of.
  3. If there was just something else entirely that failed - solar flares or something that was really outside of your control.

But you don't bother recovering and trying to keep running - as everyone else said, if you can't recover then you really shouldn't. But you don't have to die ungracefully - it's OK to give a last gasp, "Well, that was unexpected!" before keeling over. And in my experience, that's even a good idea because then you can try to fix it or account for the problem. Or just decide that it was something that really wasn't recoverable and you're just going to alert on that or something.

1
  • 3
    One of my favorite things about python is that it already does what I want by default. Uncaught exceptions write a backtrace to stderr, and exiting the process with return code 1. I only ever catching bear exception when I want to "backtrack and die loudly" with a special-purpose integration. For *nix systems, stderr and rc!=0 is integrated, crontab will mail stderr on failure, the system supervisor writes stderr to log files and logs failures, most (shell based) task runners understand this behavior well. Commented Jan 7, 2020 at 4:36
1

You have to consider the reason/purpose for handling an exception, such as:

  • To perform some sort of corrective or recovery action so that the application can meaningfully continue; this implies that the exception and its consequences are well understood
  • To perform some sort of cleanup so the application is restored to a consistent state; again, the consequences of the exception must be fully understood
  • To capture information about the exception for logging and later analysis
  • To fail gracefully, such as to issue some sort of user-friendly/user-meaningful message in an appropriate UI instead of whatever raw message or code is directly reported by the exception; in doing this, it's generally wise to capture the more technical details for analysis by the development team.

If there is no useful purpose served by your exception handler, then a handler shouldn't be there and the exception should be allowed to "bubble up".

"Bonehead" exceptions implies conditions which were not anticipated when the application was initially coded. Un-anticipated exceptions should have as much detail captured as feasible so that the cause can be determined and appropriate action taken. This may be done automatically by the programming framework; if so, then you may not want to explicitly handle them.

1

I know I am late to the party, but I think there is some nuance that is missing in the existing answers. To quote sage advice from any number of martial arts movies:

The best way to block a punch is to not be there

To bring it into software engineering, we can repurpose the quote this way:

The best way to handle exceptions is to never throw them in the first place

Even though it was never explicitly called out in the first link you provided, that was essentially what the writer was trying to steer the readers toward.

Reasons why exceptions are considered bad:

  • They are very heavy to recover from (stack information takes time to unwind)
  • They were never designed to be used for normal program flow
  • They were designed to allow you to do your best effort to recover
    • That recovery may only be to close open resources and release locks before shutting down

That said, exceptions should not be propagated to users, particularly in a web context:

  • It exposes the internal architecture of your application, making it vulnerable to future attacks
  • For the web, instead of more appropriate responses like a 400 Bad Request or 404 Not Found, you return a 500 undefined server error

So what do we do?

  • Minimize the use of exceptions whenever possible (i.e. a TryParse that returns boolean instead of Parse which throws an exception)
  • Be purposeful about our exceptions
  • Never propagate exceptions to the response, but prefer logging to debug with
  • Fix the bugs that cause exceptions to be thrown
  • Verify your environment in code during startup if you have external dependencies--it's better to fail on start up then figure out at runtime that the service or application couldn't have worked anyway.
-3

This cannot be more straightforward.

There are only 3 types of code:

  1. Code that never throws an exception.

  2. Code that you know will generate exceptions. For example, connecting to an external RPC server, it is expected that sometimes it will timeout. You should add try...catch for this type of code.

  3. Code that you didn't expect, but does throw errors. Since you didn't expect it to throw errors, there's nowhere to add try...catch, because you cannot add an infinite number of try...catch, that's simply not possible. Once an error happens, you just debug and fix it, that's all.

How hard is that?

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.