I agree the article might have been more explicit there.
If I'm understanding correctly, it's all about possibility of this statement throwing an exception, as for example the cool.ico file might not be found or might be corrupt:
icon.Icon = new Icon(GetType(), "cool.ico");
I think the point is that the 'bad' version can make the icon visible in the UI and then throw an exception. The 'not bad' version has a more transactional flavour: if the aforementioned statement throws, then, because of the better ordering, the 'not bad' version doesn't make the unfinished icon object visible, it just bails out with the exception having made no change to the UI, and the unfinished NotifyIcon instance gets garbage-collected.
I presume that the real-world code included some extra machinery to hook it up to the existing UI objects, omitted for brevity in the example. It's a bit confusing as it looks rather like it's just building up and returning a NotifyIcon instance for the caller to make use of, but I think the icon.Visible = true; is meant to represent truly making the icon visible in the UI.
Thanks. Then the 'bad' code should look something like:
// bad
NotifyIcon CreateNotifyIcon()
{
NotifyIcon icon = new NotifyIcon();
icon.Text = "Blah blah blah";
system.Display(icon);
icon.Icon = new Icon(GetType(), "cool.ico");
return icon;
}
Which would make noticing the coding error quite a bit easier, as we are passing an obviously incompletely initialized object to a third party. As presented, the code presumably is intended to be used like so:
which works in both versions. If 'icon.Icon = new Icon(GetType(), "cool.ico");' throws, then the NotifyIcon is never displayed.
As presented, this is hardly an argument that explicit error passing code is better at detecting bad code than exception based code. His other article, https://devblogs.microsoft.com/oldnewthing/20040422-00/?p=39..., is making the point a bit more clearly.
The point itself is half-sound. While it is true that with exception based code, it is hard to distinguish which statements may fail, the real issue is about correctly releasing resources. Assume that every statement can fail, and use some form RAII to manage resource cleanup. The exact content of RAII is hard to glean in both explicit error code and exception based code, as it depends on how the specific third party API acquires/releases resources. Though APIs can be organized to essentially force the use of RAII, even if popular but ancient APIs like POSIX file system are not designed that way.
The real language design issue is that Java/C# mechanism for running cleanup code 'try {} finally {}' a. fails to pass through a handle to the resource that needs cleanup and b. is not scoping the lifetime of the resource that needs cleanup. Furthermore, the standard API makes no effort to provide RAII constructs for correctly managing resource lifetimes. Those language ecosystems actively steer people towards writing bad code. Exceptions may be fine, but the lack of language supported RAII is definitely poor language design. For better language design, exception based Python offers 'with' mechanism and error code based Golang offers 'defer'.
> Assume that every statement can fail, and use some form RAII to manage resource cleanup.
I think that's part of Chen's point: programmers just aren't good at doing this, they even get it wrong in published code samples. Exception-handling tends to become an afterthought, and even if you do pay attention to it, it's hard to get right.
Chen is hardly alone in his scepticism. Exceptions are forbidden in the Google C++ style guide. They're also forbidden in certain critical-systems subsets of languages. Ravenscar Ada forbids exceptions, [0] as does Spark Ada (though in that case it's for a slightly different reason: it's difficult to formally reason about exceptions). edit Apparently the JSF C++ standard forbids exceptions, but MISRA C++ permits them provided certain guidelines are followed.
I agree that RAII is very useful for robust exception-handling.
> APIs can be organized to essentially force the use of RAII, even if popular but ancient APIs like POSIX file system are not designed that way
Right, this is just the kind of thing C++ wrappers add (when wrapping C APIs).
> Java/C# mechanism for running cleanup code 'try {} finally {}' a. fails to pass through a handle to the resource that needs cleanup
Short of proper RAII (destructors), I'm not sure what that would look like.
I'm not sure what Chen makes of destructors. They're non-local flow-control, but he seems to like them.
Somewhat related: Zig's optional types, which essentially force the programmer to explicitly handle the case where the data doesn't exist. [1] Much more robust than the approach C takes, where the programmer is trusted to perform the check when necessary.
RAII can be decoupled from ctor/dtor mechanism, see Python's 'with' statement. Javaish pseudocode, similar to a 'for' loop:
// on exit the finally fn will be called with 'x' as an argument.
with (Type x: create(args); finally (Type x) -> cleanup(x)) {
// safely use x, throw at will.
}
Re C++ and exceptions, there is an additional layer of wrinkles: throwing exceptions from ctors or, worse, dtors. Herb Sutter used to have a loooong list of what can go wrong in such situations back in the day. Explicit error codes happen to make it impossible to write ctors / dtors that can enter an error state by virtue of the fact that there is no way to return the error. My suspicion is that this is 50% of the reason of banning C++ exceptions in solid C++ style guides.
> RAII can be decoupled from ctor/dtor mechanism, see Python's 'with' statement
That may be a useful language feature, but I don't count that as full RAII. I'm inclined to agree with Wikipedia's definition that the destructor must run whenever the object's lifetime ends, to count as proper RAII:
> resource deallocation (release) is done during object destruction (specifically finalization), by the destructor
As x can presumably leak from the with block of your example, it isn't full RAII. I think it's equivalent to the confusingly named using feature of C#. [0]
> throwing exceptions from ctors or, worse, dtors. Herb Sutter used to have a loooong list of what can go wrong in such situations
Thanks for the pointer ( or should that be reference? :-P ), these are an interesting read. [1][2][3]
> Explicit error codes happen to make it impossible to write ctors / dtors that can enter an error state by virtue of the fact that there is no way to return the error
Yes, but this approach detracts from the RAII somewhat, as it means the programmer must move the substantial code out of the constructor and into a post-construction initialization member-function capable of throwing. That may be a price worth paying, but I do consider it a price, as we've lost a useful invariant: we must now keep track of which instances are in the constructed-but-unready state. An unfortunate slide back toward C.
> My suspicion is that this is 50% of the reason of banning C++ exceptions in solid C++ style guides.
I suspect these weird edge-cases where intuition diverges from reality, are much of the motivation for those prohibitions, yes.
If I'm understanding correctly, it's all about possibility of this statement throwing an exception, as for example the cool.ico file might not be found or might be corrupt:
I think the point is that the 'bad' version can make the icon visible in the UI and then throw an exception. The 'not bad' version has a more transactional flavour: if the aforementioned statement throws, then, because of the better ordering, the 'not bad' version doesn't make the unfinished icon object visible, it just bails out with the exception having made no change to the UI, and the unfinished NotifyIcon instance gets garbage-collected.I presume that the real-world code included some extra machinery to hook it up to the existing UI objects, omitted for brevity in the example. It's a bit confusing as it looks rather like it's just building up and returning a NotifyIcon instance for the caller to make use of, but I think the icon.Visible = true; is meant to represent truly making the icon visible in the UI.