Hacker News new | past | comments | ask | show | jobs | submit login
The error model of Microsoft's new language (plus.google.com)
78 points by thomasz on Dec 30, 2013 | hide | past | favorite | 34 comments



Deeming certain errors to be unrecoverable in a component means that software will be designed such that "components" will be bundles of functionality with tight dependencies. This is interesting, and forces a separation across component boundaries that we should do anyway.

Consider a web server. If we tried to make the entire web server one component, then hitting an out-of-bounds error when handling a request would take down the entire web server. That is bad. But if the main event loop of the web server is a component that depends on a separate component that handles requests, then the main event loop could recover from the out-of-bounds error in the page request component. This is what we should always do, of course, but it's being forced (or, heavily encouraged) by the programming model itself.

I'd like to see how "components" are defined to understand this better - or any code, really.


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:

  if (x == null) { /* handle null case */ }


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.


>It seems to conflate two orthogonal concepts

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).


Here's a link to a video [0](2009) where Joe Duffy talks about some features he's exploring adding to C#. Mostly, he describes a method of organizing code into distinct object graphs to make concurrency easier to reason about.

[0] http://channel9.msdn.com/Shows/Going+Deep/Joe-Duffy-Perspect...


Isn't this tight coupling the issue with node js?


A few years ago I was debating with a colleague on the de/merits of checked exceptions. I was pro, but after reading through these gems about why Microsoft didn't put them into c# I changed my mind: http://blogs.msdn.com/b/csharpfaq/archive/2004/03/12/why-doe...


Wow, those are some interesting reads.


Seems to have a lot in common with the error handling in D.

They both make the distinction between recoverable and unrecoverable errors. I feel D to be more flexible however since it doesn't prevent the handling of unrecoverable errors. It simply discourage it, which is what you want in a systems programming language.

One exceptional feature of D related to error handling are scope statements. They easily and elegantly make the nested try/catch/finally blocks go away. I wonder if Microsoft is going to use this.


It's been a while since I looked at D (10 years or so)... Is the D scope + unrecoverable error different from Go's "defer" and "panic"?


D changed a LOT in 10 years, especially since D2.0 has superseded D1.0 and added tons of fantastic features. It's definitely worth a look!

Did a quick lookup on Go's defer and panic, it does indeed have similarities with D's scope. Go's defer looks like the equivalent of scope(exit) in D.

The big difference is that error handling in Go uses error codes while D uses a Throwable base class subclassed into Exception and Error for recoverable and unrecoverable errors respectively.

D also has scope(success) and scope(failure) to execute code blocks depending on whether a Throwable is raised or not.


>Is the D scope + unrecoverable error different from Go's "defer" and "panic"?

D's scope guard is similar to "defer", but it has three forms: "scope(exit)", "scope(failure)", and "scope(success)". So instead of a deferred function that tests "if r := recover(); r != nil {...}" you just do "scope(failure) {...}"

D's unrecoverable error is just a matter of having an exception hierarchy where "Exception" and "Error" are separate subclasses of "Throwable", and by convention you prefer not to write "catch (Throwable t)" or "catch (Error e)".


I'm very happy to see that contracts play such a central role in this language. I hope that this will mean that contracts will finally gain more popularity, if Microsoft manages to push this language well enough.

I feel that 70% of what I write in unit tests and ifs-at-the-start-of-a-method should be in contracts, and the only reason they're expressed so cumbersome is because the languages I use don't support any better.


ifs-at-the-start-of-a-method

Even for a simple if: DRY! You should have put those in reusable functions with nice understandable names ages ago :]


do you know what contracts are?

Typical contracts are 'assert (arg != null)' or 'assert(arg1.length == arg2.length)' or 'assert (hour >= 0 and hour <= 23)'


yes I know what they are. Use them all the time. But since the post is about if I imagine it's about things like

  if( x < 0 || x > 5 )
    throw new ArgumentOutOfRange();
  if( y == nullptr )
    thwrow new NullRefException();
which should imo be something like

  Contract.AssertInRange( x, 0, 5 );
  Contract.AssertNotNull( y );


I imagine you could do even better with something like PostSharp or Fody.

    public void MyFunction([InRange(0, 5)] int x, [NotNull] object y)
    {
        // ...
    }
I just checked, and in fact Fody can do something just like this [1].

The only downside I see is contracts like this would be hard to write concisely:

    if (useFirstObject == true)
        Contract.AssertNotNull(firstObject);
    else
        Contract.AssertNotNull(secondObject);
[1] https://github.com/Fody/NullGuard


It is possible to write your own contract abbreviators with Code Contracts so I would hope such functionality gets added to the new language. The post does mention however that object invariants are not supported so I do wonder how much Code Contracts are actually influencing it.


Aleks replied about contracts (in the comments below the article):

  We definitely tried to learn from Eiffel. Our contract 
  system really isn't revolutionary at all; we just tried
  to take the simplest and best parts of Eiffel, and 
  distill them down into a format that would feel familiar
  and comfortable for C# developers.


Old school C hacker here. Can someone explain to me like I'm five how this is different from

assert(p != NULL); // unrecoverable error

if (p == NULL) {

    // no buffer for you but we'll continue

    perror("malloc");

    return (0);

}


Yes, see my top-level comment in this thread. The key notion is that such errors are unrecoverable in a component, but you can recover from outside that component. So, if you are in component A, and you make a call to component B, and B has an unrecoverable error, B dies, but A can still recover.

Without code, though, it's hard to see how this will actually work.


Seems like if you used processes to implement components and had a master process to respawn when a "component" dies then it is just assert() vs perror().

That's hand waving past any shared state but if you need that I suppose you use threads (I'm not a thread fan but I have to believe there is a thread_assert() that kills just that thread).


Introducing threads and multiple processes seems slightly crazy. Remember that ideally the most common case is no failure at all and you don't want to introduce cost for that. Why not just have the point of failure unwind the stack until it sees a component boundary? Without multiple address spaces certain types of failure would blow up the whole process but that's life.


That's how Erlang OTP does it, IIUC.


I'm a bit dubious without seeing some concrete code. What I see is a lot of strong assumptions being made and usually when that happens the result is leaky abstractions. We already have extremely leaky abstractions in every other error model out there, so I'm concerned this model is merely shifting the complexity around.


By your own admission, all the existing error models are janky.

I'd think that should be a reason to encourage people who are trying new things in an effort to come up with one that works well, not a reason to offhandedly poo-poo anyone who isn't satisfied with the acknowledgedly crummy status quo.


Sure, but there's a lot of handwaving about this new error model. If there were code examples it would be a lot easier to form a solid opinion one way or the other.


I'm not sure we should expect anything but handwaving. This language hasn't been released to the world; it may be that it's not even a fully-functional language yet. All we've got to go on is a couple sentences from a blog post that clearly wasn't intended as a detailed exposition, a G+ post from someone who is no longer involved in the project, and a whole lot of Reddit. Mostly the Reddit.

More details will hopefully be coming soon, but until then we might as well stay calm and try not to confuse cocktail napkins with something that's ready for peer review.


Would love to see some code examples. Has any surfaced yet?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: