But do we really want to be forced to design components based on whether errors are unrecoverable or not? It seems to conflate two orthogonal concepts. I say that without seeing any code or examples, of course.
It's a yoke I'd be pretty happy to accept as an application developer. No, it's not necessary to couple the two concepts. But nine tenths of good language design is coming up with good abstractions, and good abstractions always involve some conflation. And from a software engineering perspective I can see some good reasons for this choice.
One is that it creates some predictability around how errors will be handled. As a producer of components, I know that within this component I have to accept that certain classes of error will always cause a fast failure, and other classes of error should be handled as immediately and gracefully as possible. Compared to the mishmash of different error handling philosophies that I currently encounter when working on different components, this seems like it might be a welcome improvement even if it isn't a complete solution.
As a consumer of components, I think it's even better because it enforces still stronger predictability. I get a couple nice guarantees out of this. One is that critical failures bringing the whole component down means I encounter fewer situations where I'm unsure whether the overall application is in a consistent state, because 3rd-party component developers have fewer opportunities to do weird things with exceptions. The other is that I can always defend against critical failures in 3rd-party components bringing down the whole application. That isn't always easy to do in vanilla C#, where FailFast exceptions always bring down the house and the best defense against that is to resort to the hassle of process-level isolation. For systems programming I imagine such measures are even more irritating, since process-level isolation creates performance overhead.
As I replied below, my argument is not that reasoning and controlling error handling is not important (I spent a long time in real-time, critical systems such as avionics). It is that components doesn't strike me as the most commodious way to express my decisions on error handling (I may be wrong, I haven't seen the code).
Maybe we do. Can you convince yourself that we should not design software in such a way that we are forced to reason about at which granularity errors are recoverable?
That is not my argument or thinking. I may, for example, want one component, but it has two types (or more) or error reporting that I want. I am now forced to decouple that into two components. But, it shouldn't be two components, otherwise I would have already made it two components. Furthermore, anything that should be 2 or more components is rather unlikely to also require exactly the same kind of split for error handling. I anticipate the result being a bunch of artificially split up components, each which knows way too much about other components (because they should have been one component).
It just sounds like a mess. Not the worst mess in the world, to be sure, but error handling just isn't the way that I want to be forced to architect my system. Imagine, for example, I declared that primitive types are important, and thus you have to create components that either work with int, or double (or char vs unicode, or whatever). For some project you might just split things up like that anyway, but it would be pretty unusual in most cases. Or, if that seems contrived, I say const correctness is important, and thus components must be const or not. Elaborate with any language feature you consider important...
tl;dr: Yes, think long and hard about error handling. But no, I don't necessarily want/need/like organizing code into components based on that need. Error handling is only one of many competing pressures on how I might organize code.
Judging by what we know of the kinds of errors that they're talking about having be uncatchable, I think you might be overstating the worry a bit. Sounds like the list is constrained to failures that really do mean a bug in the code such that something is seriously wrong, let's get the heck out of here is probably the only appropriate response if you're trying to build a robust system.
Take the null reference example: A NullReferenceException means that a value's null in a spot where the programmer either didn't believe or didn't consider that null would be a possible value at that point in the program. So when it happens, there are two things you can be sure of: First, things have strayed off into the weeds. Second, there's nobody around to check everything out and make sure we can get steered safely back on track, because the developer's been out of the picture since the start of compile time. These two facts imply that letting execution continue would be A Roll of the Dice, and therefore Not a Good Idea.
If having the module crash in this situation isn't acceptable, no worries. There are still ways around it short of splitting the component in two. One much easier option would be:
An example would be a Web request in a concurrent app server: if one request has an error accessing the database, maybe it can't continue, but the server process should keep handling other requests.
Generally, I think a "component" would be a piece such that if there's an unplanned-for, fatal failure in it, other components need to continue running.
That is not necessarily bad. You only want to keep orthogonal concepts independent if mixing them doesn't buy you something better (in this case that would be a design that defaults toward fault tolerance).
Comments
But do we really want to be forced to design components based on whether errors are unrecoverable or not? It seems to conflate two orthogonal concepts. I say that without seeing any code or examples, of course.
It's a yoke I'd be pretty happy to accept as an application developer. No, it's not necessary to couple the two concepts. But nine tenths of good language design is coming up with good abstractions, and good abstractions always involve some conflation. And from a software engineering perspective I can see some good reasons for this choice.
One is that it creates some predictability around how errors will be handled. As a producer of components, I know that within this component I have to accept that certain classes of error will always cause a fast failure, and other classes of error should be handled as immediately and gracefully as possible. Compared to the mishmash of different error handling philosophies that I currently encounter when working on different components, this seems like it might be a welcome improvement even if it isn't a complete solution.
As a consumer of components, I think it's even better because it enforces still stronger predictability. I get a couple nice guarantees out of this. One is that critical failures bringing the whole component down means I encounter fewer situations where I'm unsure whether the overall application is in a consistent state, because 3rd-party component developers have fewer opportunities to do weird things with exceptions. The other is that I can always defend against critical failures in 3rd-party components bringing down the whole application. That isn't always easy to do in vanilla C#, where FailFast exceptions always bring down the house and the best defense against that is to resort to the hassle of process-level isolation. For systems programming I imagine such measures are even more irritating, since process-level isolation creates performance overhead.
As I replied below, my argument is not that reasoning and controlling error handling is not important (I spent a long time in real-time, critical systems such as avionics). It is that components doesn't strike me as the most commodious way to express my decisions on error handling (I may be wrong, I haven't seen the code).
Maybe we do. Can you convince yourself that we should not design software in such a way that we are forced to reason about at which granularity errors are recoverable?
That is not my argument or thinking. I may, for example, want one component, but it has two types (or more) or error reporting that I want. I am now forced to decouple that into two components. But, it shouldn't be two components, otherwise I would have already made it two components. Furthermore, anything that should be 2 or more components is rather unlikely to also require exactly the same kind of split for error handling. I anticipate the result being a bunch of artificially split up components, each which knows way too much about other components (because they should have been one component).
It just sounds like a mess. Not the worst mess in the world, to be sure, but error handling just isn't the way that I want to be forced to architect my system. Imagine, for example, I declared that primitive types are important, and thus you have to create components that either work with int, or double (or char vs unicode, or whatever). For some project you might just split things up like that anyway, but it would be pretty unusual in most cases. Or, if that seems contrived, I say const correctness is important, and thus components must be const or not. Elaborate with any language feature you consider important...
tl;dr: Yes, think long and hard about error handling. But no, I don't necessarily want/need/like organizing code into components based on that need. Error handling is only one of many competing pressures on how I might organize code.
Judging by what we know of the kinds of errors that they're talking about having be uncatchable, I think you might be overstating the worry a bit. Sounds like the list is constrained to failures that really do mean a bug in the code such that something is seriously wrong, let's get the heck out of here is probably the only appropriate response if you're trying to build a robust system.
Take the null reference example: A NullReferenceException means that a value's null in a spot where the programmer either didn't believe or didn't consider that null would be a possible value at that point in the program. So when it happens, there are two things you can be sure of: First, things have strayed off into the weeds. Second, there's nobody around to check everything out and make sure we can get steered safely back on track, because the developer's been out of the picture since the start of compile time. These two facts imply that letting execution continue would be A Roll of the Dice, and therefore Not a Good Idea.
If having the module crash in this situation isn't acceptable, no worries. There are still ways around it short of splitting the component in two. One much easier option would be:
An example would be a Web request in a concurrent app server: if one request has an error accessing the database, maybe it can't continue, but the server process should keep handling other requests.
Generally, I think a "component" would be a piece such that if there's an unplanned-for, fatal failure in it, other components need to continue running.
That is not necessarily bad. You only want to keep orthogonal concepts independent if mixing them doesn't buy you something better (in this case that would be a design that defaults toward fault tolerance).