If it’s possible to do something, then it’s possible to do something WRONG


Once you make it possible to do something, you have to accept that you also made it possible to do something wrong.

When the window manager was originally designed, it made it possible for programs to override many standard behaviors. They could handle the WM_NC­HIT­TEST message so a window can be dragged by grabbing any part of the window, not just the caption bar. They could handle the WM_NC­PAINT message to draw custom title bars. The theory was that making all of these things possible permitted smart people to do clever things.

The downside is that it also permits stupid people to do dumb things.

Changing the window procedure model from call Def­Window­Proc to get default behavior to return whether you handled the message wouldn't have helped. First of all, the handled/not-handled model is too restrictive: It requires you to do everything (handled) or nothing (not handled). There is no option to do a little bit. (Imagine if C++ didn't let you call the base class implementation of an overridden method.)

Doing a little bit is a very common pattern. The WM_NC­HITTEST technique mentioned above, for example, uses the default hit-testing implementation, and then tweaks the result slightly:

case WM_NCHITTEST:
 // call base class first
 lres = DefWindowProc(hwnd, uMsg, wParam, lParam);
 // tweak the result
 if (lres == HTCLIENT) lres = HTCAPTION;
 return lres;

How would you do this with the handled/not-handled model?

case WM_NCHITTEST:
 if (not handling this message would have resulted in HTCLIENT) {
  lres = HTCAPTION;
  handled = TRUE;
 } else {
  handled = FALSE;
 }
 break;

The trick about that bit in parentheses is that it requires the research department to finish the final details on that time machine they've been working on. It's basically saying, "Return not handled, then follow the message until handling is complete and if the final result is HTCLIENT, then fire up the time machine and rewind to this point so I can change my mind and return handled instead."

And even if the research department comes through with that time machine, the handled/not-handled model doesn't even solve the original problem!

The original problem was people failing to call Def­Window­Proc when they decided that they didn't want to handle a message. In the handled/not-handled model, the equivalent problem would be people returning handled = TRUE unconditionally.

BOOL NewStyleWindowProc(HWND hwnd, UINT uMsg,
 WPARAM wParam, LPARAM lParam, LRESULT& lres)
{
 BOOL handled = TRUE;
 switch (uMsg) {
 case WM_THIS: ...; break;
 case WM_THAT: ...; break;
 // no "default: handled = FALSE; break;"
 }
 return handled;
}

(Side note: The dialog manager uses the handled/not-handled model, and some people would prefer that it use the Def­Xxx­Proc model, so you might say "We tried that, and some people didn't like it.")

This topic raises another one of those "No matter what you do, somebody will call you an idiot" dilemmas. On the one side, there's the Windows should perform extra testing at runtime to detect bad applications school, and on the other side, there's the Windows should get rid of all the code whose sole purpose in life is to detect bad applications school.

Comments (22)
  1. John says:

    I was not specifically calling for the removal of that code; all I was saying is that at some point the cost of maintaining backward compatibility outweighs the benefit.  The larger point is that if you continue to allow people to do something wrong, they will continue to do it wrong.

  2. Andrew says:

    That quote should be the first thing every developer learns.

  3. 640k says:

    With good docs & apis the probability for wrongness decreases.

  4. Adam says:

    <quote name="640k">

    With good docs & apis the probability for wrongness decreases.

    </quote>

    No, nothing can help the ignorant.  They will always blindly find the one way of misusing your API for something that you never intended, then demand that your "feature" be supported.

  5. 640k says:

    @640k: The probability that someone will not read them remains, unfortunately, fairly static.

  6. Paul M. Parks says:

    ACK! 640 did not post the comment about people not reading documentation, I did. I didn't read the documentation above the "Name" text box. Sorry, 640k.

  7. Cesar says:

    > "Return not handled, then follow the message until handling is complete and if the final result is HTCLIENT, then fire up the time machine and rewind to this point so I can change my mind and return handled instead."

    I think the time machine you are talking about already exists, and is called "call/cc". You just have to return a continuation, and have the message handling call it with the final result when it is finished. The control flow will magically rewind to the correct place, and now you know the final result. Easy, right?

    (Note: of course I am joking. Implementing call/cc in C would be insane. And I am not responsible if anyone decides to google "call/cc" and ends up with a headache.)

    [call/cc is not a time machine. In this case, it's a "back-end hook", which logically brings us back to where we started. (Code running before and after the default behavior.) -Raymond]
  8. Mike Caron says:

    @Cesar: Bringing it back to the original problem, how does this help the "always return handled" case?

  9. Burak KALAYCI says:

    > (Imagine if C++ didn't let you call the base class implementation of an overridden method.)

    It makes no difference… to me…

    Because I don't use C++.

    I proudly use Delphi.

    And if Delphi didn't let me call the base class implementation of an overridden method, OMG! I get it now.

    I tend to forget the official language of this blog is C/C++…

    [I choose a language that most of my readers are likely to understand. Sorry it's not your preferred language. -Raymond]
  10. Cesar says:

    @Mike Caron:

    how does this help the "always return handled" case?

    It doesn't. It, however, either satisfies both sides (we have BOTH the Def*Proc and the "return handled" options!) or annoys both sides (if they think being able to do it the other way is wrong). It is a democratic option ;-)

  11. Burak KALAYCI says:

    [I choose a language that most of my readers are likely to understand. Sorry it's not your preferred language. -Raymond]

    Dear Raymond, as you probably are aware, my post was to illustrate the C/C++ bias in your posts. You did not have to specify any language at all, because the concept applies to all languages which provide OOP, but you did say C++ and I bet you did it just to eliminate C there (as if no other language existed).

    And I'm sorry Delphi isn't your preferred language :)

    [I did it to pre-empty nitpickers who would say "Your sentence is wrong because not all languages have the concept of base classes." Even if I had written "in a language which provides OOP", the nitpick would be "Your sentence is wrong because programming language X is OOP but does not provide for explicit invoke of the base class." I specifically said "C++" so that the statement would be correct. This is not mathematical research, where the you want to maximally weaken the assumptions in order to make the conclusion as strong as possible. -Raymond]
  12. 640k says:

    IntelliTrace can run code backwards. There's you time machine.

  13. 640k says:

    Dave Mendlen, Senior Director at Microsoft, refers to IntelliTrace as a “time machine.”

    msdn.microsoft.com/…/gg512834.aspx

    [Yeah, but it's a read-only time machine. You can't go back in time, change a variable, and then resume execution from that point with the new value. -Raymond]
  14. Gabe says:

    I'm sure it's just a small matter of coding to turn a read-only time machine into a read-write version. Right?

  15. Joshua says:

    We are in possession of a time machine. At this time we have dared not activate it yet because we don't know how to control it and don't want to end up on Camezotz.

    We have already determined it cannot be controlled roboticly.

  16. mikeb says:

    A new item to add to my collection of "life rules":

     – Chen's Theorem of Ultimate Optimization: You can do nothing really fast

     – Chen's Law of Provable Correctness:      If you don't do anything, you can't do it wrong

     – Chen's Inverse Correctness Corollary:    If it's possible to do something, then it's possible to do something WRONG

  17. Cesar says:

    As an aside: I think you could easily combine the Def*Proc model with the "return handled" model.

    Just call the Def*Proc if the program returned "not handled". You could still call it by hand and return "handled" if you wanted.

  18. Cheong says:

    @mikeb: That's extension to Murphy's law that every programmer ought to take into account whenever designing software.

  19. Humus says:

    On the one side, there's the Windows should perform extra testing at runtime to detect

    bad applications school, and on the other side, there's the Windows should get rid of

    all the code whose sole purpose in life is to detect bad applications school.

    Let's do both!

  20. Ooh says:

    @Humus: LOL! Too good to be true :)

  21. mikeb says:

    @Cheong: I see.  I'll have to start reading this guy Murphy's blog.

  22. Drak says:

    @MikeB: Your theorems have made it onto the office wall :)

Comments are closed.