Hacker News new | ask | show | jobs
by tilt_error 1184 days ago
Back in the day, I developed Windows applications using Visual C++ and Microsoft Foundation Classes (MFC). MFC had its set of quirks, and development was quite painful - I often wondered why a particular method was non-virtual, which would have helped me to adapt to certain requirements. I spent more time stepping through MFC code than in my own code, trying to understand what was going on.

Eventually, I moved to other platforms and programming languages and had an epiphany that to function as a programmer of Windows software, I needed to know a lot of details about the platform. The literature was overflowing with articles about how you could utilize the platform more efficiently if only you knew some details under the hood. Moving away from Windows programming freed up a lot of mental resources.

When I started programming in Java on top of all libraries and frameworks, I noticed that I was never really encouraged to look beyond the API. On the other hand, there were usually multiple implementations of those APIs, so if I wasn't satisfied with one, I could choose another.

Of course, building GUIs in Java was (and still is) a real mess, so I didn't do that. Since then, I have programmed quite a bit in C# with the classic Forms-based platform on .NET, and this was a totally different experience from C++ and MFC. It was a delight.

Reading this article was like traveling back in time to the olden days, where you are promised better performance if only you look under the hood. My honest suggestion is: don't do that, don't lock up resources on needing to understand how things are implemented under the hood or how the compiler handles these particular constructs. If these details cannot be hidden from you, and you need to build a large knowledge base around stuff that does not directly contribute to implementing your program, then choose another platform.

I have managed to accumulate a growing understanding of how to solve problems, many times transferable between platforms and languages, but collecting all that insight into MFC minutiae was completely worthless.

3 comments

One of my favorite things about .NET now being open source (along with the frameworks like ASP.NET) is that I can easily 'peek under the hood' for various functions that I use for edge cases/details. For example, with int.TryParse(myString, out int i), what happens if myString is NULL? The docs for most of the core .NET libraries are pretty good, but nothing beats quickly parsing through the actual code. This is especially true for various constructs in ASP.NET, where the documentation frequently lags behind newer versions, and by nature, can't capture all of the possible options.

Digging deep into the core libs also makes you a better programmer by renforcing idiomatic patterns and exposing you to new techniques. Just the other day I dug into how ArgumentNullException.ThrowIfNull works, and discovered the newish (C# 10) attribute [CallerArgumentExpression("argument")] which gives you a string with the expression used to pass that parameter (i.e. in most cases, just the original variable name.) This has now entered my mental toolbox for stuff like validation code. The docs for this are actually quite good (https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...), but one has to know it's there. Often, reading other code is the best way to discover new features/techniques, and what better code to read than core .NET itself.

> MFC had its set of quirks, and development was quite painful

I had the same experience. And then I learned win32, the C predecessor of MFC, and it all started to make sense.

MFC is basically a horrible attempt to put an Object Oriented layer on top of win32. But they failed miserably at it. The API was so bad that I preferred just writing it in win32.

And no, this was not a failure of OO, this was a failure of understanding and applying OO.

The message map macros were the equivalent of the switch statement in a WndProc. They couldn’t add every Windows message as a virtual override because every derived class would have needed thousands of vtable entries. At the time memory was still very limited (4MB systems for Windows 95 and they also had support for 16 bit Windows where it would have been even worse).

Also the template support in the compiler was limited at the time. Later on there were other libraries that used templates like ATL and WTL.

You did get a lot for free from MFC when implementing document editing applications for OLE support, but that was an increasingly small portion of development.

I think Borland went a different way with compiler extensions for producing a Win32 application with their OWL library.

It wasn't a failure of understanding. It was a deliberately low-level architecture that could be mapped extremely close to the underlying Win32 API for minimal overhead, and give the developer full control over what happens when, while providing some convenience layer especially to deal with common / recommended patterns at the time (document-centric UI, drag and drop, OLE etc).

I agree that this particular combination of features was not as useful in practice as the higher-level and more free-form approach offered by e.g. Delphi. But that's a different story.

I think the idea behind such high level APIs is that they work well enough and are easier to use in 9X% of the cases and you can still get perf out of them if you look under the hood.

For the reverse of your example, consider Java and .NET GCs. Most people don't need to tune Java GC and can just copy some config off stack overflow, however Java GC (GC-s, actually) tuning for perf and reading its verbose log is a whole dark art and there are tons of really long and verbose articles and example stories online. .NET GC is much more opaque in my experience, and that is really annoying when it doesn't "just work".

async await makes writing async code stupid easy most of the time... much easier than in Java, although I haven't used Java in a few years, it could have improved. However when it doesn't quite work you need to look under the hood.