So yeah, fail fast/halt catch fire on any invalid input/condition is my personal preferred design, but I can see the value in this approach in this environmental context. The important thing here is that context and not applying either dogmatically. Don't take Mr Chen's approach in reactor or HFT designs for example. Fantastic approach for game engines.
Recently I had to read an old Access file and where I work we still keep Office '97 around for this purpose and it is quite amazing that it installs and works just fine on Win 11, Clippy works just fine, in fact all the other lame-ass things Office '97 does to take over your desktop all still work even if they don't quite visually match the current desktop.
The thing is that Microsoft has a framework built into Windows where they can specify different system behaviors such as that Simcity case where the game didn't work with a new memory allocator so they put in a flag so an application that needs it can get the old memory allocator. They have a very systematic approach to the half-baked and haphazard process of patching the OS so you don't have to patch applications.
Here's a pretty detailed list:
- It is possible to target Windows XP SP3 (released 2008, EOL 2014, 10 years ago) from Windows 11 and Visual Studio 2022[1] using C++23. Windows 2000 can be targeted with a little more setup[2]. Windows 2000 is 24 years old this year.
- It is possible to run a binary written for and compiled in Windows NT 3.1 x86 on a modern 2024 Intel/AMD PC running Windows 11 with absolutely no modifications to said binary whatsoever. Windows NT 3.1 is 31 years old this year.
- It is possible to write one binary and target multiple versions of Windows by simply choosing a Platform Toolset, which is paired with a Visual C/C++ Redistributable.
- Windows has a run-as mode to run programs in what is essentially a super-thin VM; like you mentioned, emulating different iterations' behaviour for a given program.
All four of these are nearly impossible on Linux. The fourth is essentially Docker, which is needed even to target an older glibc (i.e. the equivalent of the first situation). Windows has gone to extreme lengths to not only maintain API compatibility, but ABI compatibility as well. The fact that unmodified binaries from 20, 25, 30 years ago can run without a hitch is a testament to how important backwards/forwards compatibility still is on Windows.
Side tangent: all the criticisms levelled at Windows here and in many hacker fora are limited to its userspace—things like its shell and user-space programs, and trivial complaints like 'Candy Crush', or 'start bar', or 'extra right click', or even 'PowerShell aliased curl to Get-Content, grr'. The userspace changes so often because it can; it is a superficial part of Windows. To the haters: Try actually programming with NT OS primitives and even the (undocumented) NT syscalls. It is as enjoyable as UNIX system programming, and I daresay more productive.
[1]: https://learn.microsoft.com/en-sg/cpp/build/configuring-prog...
[2]: https://building.enlyze.com/posts/modern-visual-studio-meets...
I would personally make use of this, I know of a 16-bit program whose latest version was released in the late XP days, so it's not even that old. The idea there was that it was always compatible with DOS, some users might presumably still want to run it on DOS, and there's no point in breaking that compatibility if modern Windows can run it just fine. Then development stopped, 64 bit systems got more popular, and a recompiled version was never released.
I guess the lesson there is that if you're keeping an API for backwards compatibility, some programmers will refuse to switch to a replacement to make their software work on older systems, making the API impossible to remove down the line without breaking modern programs.
Day zero feature availability, without compatibility issues.
I haven't tried running too many old programs, though, so I have no sense for how common this might be.
This seems like a norm for most operating systems. Linux mixes files from all sorts of apps all over the place, for example, and a `make install` might put files anywhere.
Somehow, it feels like you don't actually care about the point I was making as much as putting me down. I misspoke...barely, yet you're so fixated on pointing it out in the most unhelpful manner. You keep replying so why not try using more than one sentence next time?
Also, is this a trick question about binaries that weren't patched for y2k or something?
Which is weird considering the argument you had with others above about how "Linux (kernel) doesn't run software", was it a buildup to convince us that "GNU(glibc)/Linux" is really bad at running old binaries? Because your argument doesn't hold for the Linux kernel itself running statically linked binaries.
Last time I checked, hardly anyone uses their computer as an embedded board.
Not a kernel booting directly into an application doing straight syscalls.
Linux runs /sbin/init.
Which you can make any executable you want.
Not to mention initial RAMdisk (loaded by a bootloader like grub) which can be of arbitrary size & loaded up with an arbitrary ton of goodness.
But as you state: it's the OS-level ABI's (C library, desktop environments, middleware libraries, etc) that keep changing constantly. And thus, keep breaking pre-compiled binaries.
Source-level API vs. binary ABI stability. Kind of a philosophical debate imho. But sadly, even that source-level API isn't too stable in some circles.
NT seems to be a nice OS at the core, but that's more about what it can do and how it is implemented than about how pleasant it is to use. Some of its syscalls are much more convoluted than the UNIX ones. Typical functions take many parameters, some of them complicated on their own, and typical invocations pass a bunch of NULL arguments (you still need to roughly understand what they mean).
Linux has a stable ABI as well. For a given architecture, like x86_64, syscall numbers and their arguments and their arguments/outputs are expected to be stable.
The stable API/ABI promise of Linux is the syscalls, because at the end of the day that is the interface between userspace applications and the kernel, and kernel devs take not breaking userspace very seriously.
And on Windows, the opposite is true. The stable interface is the Windows DLLs that you are supposed to use dynamically. If you hardcoded the syscalls, you cannot complain if your application breaks because windows only promises that the DLL will not break backwards compatibility, and they reserve the right to change the syscalls with any update.
So you can have a Linux binary that works across many Linux versions without breaking. You can also have a Windows binary that works on one version of Windows but not the next.
The only caveats I can think of are that it must prominently display that it’s running in a “compatibility mode” and that any encrypted subsystems can’t revert to a lower standard of encryption, which may render the application unusable anyways depending on how tightly integrated it is.
I'm curious about this. What do you mean? I remember using Office '97 on a Windows '98 machine, but I don't remember Office trying to take over all my desktop.
But the primary resolution to that problem was to force hardware vendors to write higher quality drivers (or for MS to write good enough default drivers that HW vendors wouldn't need their own), not to hide the BSOD. Technical details were only removed from them 2 decades after MS started fixing drivers.
Right, but if you read the article, the author is talking less about the developer experience and more about the user experience. "blare loud sirens" is great if you're a tester/developer, not so much if you're an end user. When it comes to the end user, "swallowing errors" is preferable to crashing.
> ...when the app tries to print, it will ask the user to select a printer, and show an empty list. The user realizes, “Oh, there are no printers,” and cancels the printing request.
> To deal with apps that get fancy and say “Oh, you have no printers installed, let me help you install one,” the function for installing a printer can return immediately with a result code that means “The user cancelled the operation.”
> a documented return value is ERROR_CANCELLED to mean that the user cancelled the creation of the widget. Therefore, apps are already dealing with the possibility of widgets not being created due to conditions outside their control, so we can take advantage of that
I'm annoyed when "modern web apps" (or similar desktop apps) seemingly do nothing when there's some error, you don't know if you need to wait a bit, or click again (great fun when the UI jumps 1ms before your re-click), or full reload/restart ... luckily that's not at all what this article recommends!
If you swallow the error message, I'll have zero idea that something is even going wrong! And almost just as bad: if you put up one of those useless infantilizing "oopsie doopsie computer made a poopsie" error messages, I still won't know what went wrong AND I'm being treated like a moron.
I worked for a software company once where our software basically crashed every 2-3 hours of continuous use due to a huge backlog of technical debt, memory leaks, and years of rushing. My manager's solution to this was not to fix the bugs--it was to build a separate "launcher" process that would detect that the application crashed, eat the error messages, and silently re-launch it hoping the user doesn't notice. Way to treat your users with respect...
In many cases like this, crashes like this are in app startup, so it's not like you learn not to hit a certain button - it's just that the app doesn't work, an awful UX.
As called out in the article, there are (and always should be) APIs like "IsPrintingEnabled" so that forward-thinking apps can show better UX. These practices aren't for those apps, they're for everyone else.
Also, if your app preserves state well enough that a keep-alive daemon can restore after a crash and the user doesn't notice, that is ABSOLUTELY an improvement in UX over just crashing. Sure, you should still fix the bugs, but don't let perfect be the enemy of good.
Also he says that apps are developed and tested on PCs and that they could print in this context. I don’t know a single thing about Xbox development but I hope you can run/debug them in the Xbox environment (or a simulation).
Let me hope that nobody is running their Xbox apps/games on Windows APIs at development time and releases them on Xbox without further testing.
If a user wants to run an app on their Xbox, telling them "no, the developer didn't test this on the Xbox, so I'm not going to let you do that because you might try to print and get confused about it" isn't what the user wants to hear.
When the app tries to print because the developer was "lazy" and didn't test on Xbox, telling them "I'm going to crash your app now because you clicked Print, even though I know you're on an Xbox and I could just ignore that" also isn't helpful to the user.
It's interesting because that's true in some way (in the sense that PC and Xbox are different), and also not true in another (in the sense that a Xbox is in a way a PC, only with a different UI paradigm)
So in the latter sense UWP allows developing apps for that universal platform, and it only so happens that some apps are only designed, developed, and tested for one system (PC) these apps can run on.
In a way I can see working from the Xbox up being a better way to have a robust, secure, uniform platform than the Windows 8 attempt of slapping a secondary paradigm on top of the Windows 1.0 / OS/2 descendants.
I mean, the facility that underpins e.g WSL2 is exactly the same as the one that underpins Xbox game/apps segregation and Quick Resume. In a way the Xbox OS is very much like Qubes OS! In a way the OS UI we see on an Xbox device is a UI for the hypervisor itself.
I would certainly be interested in a "PC" that is so stable, restores state exactly upon updates, "it just works", allows to play games, allows to run a bunch of Linux VMs, with forever perfect backwards compatibility across hardware arch changes and OS evolution through virtualisation/emulation, and has a UI that allows many kinds of inputs and scales from big screen (gamepad, that accessibility input device I can't recall the name) to desktop (kb+mouse) and possibly laptop or even tablet/phone.
I mean it's not that far fetched (technically) that MS would announce tomorrow that an Xbox can run Linux, Windows 10, or even Windows 3.1 in a VM: all the facilities are there and you can even plug a keyboard + mouse today.
One may philosophically balk at the idea ("How dare you touch at my very open IBM PC! Where are my floating windows! Freedoooom!"), but I think it makes sense technically, and it makes sense as a product, and MS has all the bricks to make it happen.
This made much more sense when Microsoft had multiple platforms running windows with just different sets of apis activated.
At it's peak, this was: PC, phone, hololens, Xbox.
SMS apis may only work on phone, spatial APIs may only work on hololens, printing may work on several, but not all targets. There are ways for developers to check which APIs are supported at runtime, but you can still call these APIs since they are part of the UWP surface.
The base philosophy of "if it's wrong it should fail" is primarily for developpers, and shouldn't apply to generic customer products.
If it's dangerous, or will cost money, or will have severe ill adverse effects, I'd see the point. If a credit card transaction is wrong, make it fail.
But short of these extremes, the default should be graceful handling of exceptions and help the customer app keep going and deliver some value to the user even if it's poorly written, mishandling the context.
But I think in most cases that just isn't what's going on. An unsupported API that makes a feature not work is just not a big deal. Lack of support, say, for a cryptographic primitive, could be a big deal, so you might choose to handle that case differently.
On the other hand, we do know what will probably happen if an undocumented exception gets thrown: the app will crash, possibly causing the user to lose data.
In this specific case: it is up to Microsoft to decide what the semantics of “printing on an Xbox” are. It could be an error; or it could be something that “could be supported in theory in the future, but for now has no way to actually accomplish it.” This is a design choice, not a development choice.
After all, in theory, you could print on an Xbox — plug in a USB printer, find a “game” that knows how to print (some enhanced port of a Gameboy game that had GB Printer support, maybe?), and tell it to do so. It’s not necessarily, fundamentally an error that a Xbox game is trying to print. You could define it as an error — but that’s a choice like any other, with trade-offs (in this case, to application portability.) You could define it as asking for the user to choose from no options, as the Xbox actually does. You could have the API lie and say it printed something. You could even actually support printing. These are all choices.
It’s only once you’ve made that choice, defining the semantics of “printing on an Xbox” as an error, that it becomes an implementation/debugging problem if that error isn’t thrown — i.e. gets “swallowed.”
Anything wrong with "print to file"? There'd be no dead tree output but (file) output nonetheless & printing as a function should work.
Btw love the title of this article!
The classic one is the error code for the file open function. Early DOS would return error codes of only 3, 4, 5, and no more. DOS programs would actually just indirect-jump using the error number as an index into a lookup table of addresses. When Microsoft tried to add any error codes (say 6), the program would jump out into hyperspace since 6 was beyond that lookup table and that memory word could be anything. So Microsoft was stuck folding every possible file open error into code 5, and to this day that's why just about any file error in Windows just says "5 Access Denied". And no, Microsoft couldn't add more error codes and just let the applications break, since then nobody would buy the new operating system versions that their programs wouldn't work on.
This doesn't pass the sniff test because
1. There's documented error codes that go all the way up to 16k: https://learn.microsoft.com/en-us/windows/win32/debug/system...
2. There's file related error codes way above 5, eg. ERROR_FILE_EXISTS 80 (0x50)
The general gist of your comment is correct though. Suppose windows didn't have file locks before and they were adding it. Returning an error code like FILE_LOCKED or whatever would be much more descriptive, but would also require all existing code to handle this error case. With that in mind returning ACCESS_DENIED makes perfect sense, even if programs aren't using jump tables.
Well. I have two or three (I am not even sure because for one email address there are probably two accounts, but no way to distinguish between them) MS Teams accounts. There is no painless way to switch between them. Currently there are probably at least three different MS Teams apps installed on my machine, one of them self-installed without my consent (my PC is not managed by an org or domain, it's mine). Switching accounts involves several confusing errors, at least three password inputs, and several complaints by MS Teams that something is wrong, but it won't tell me how to fix it.
Use the browser version they said. Well for scheduled meetings it works, but for adhoc calls Firefox does not (there is no reason provided, but at least it does say outright), while Chrome seems to work but does not transmit my voice and camera. All other meeting software, of course, works.
I am not the youngest anymore and this is my lifelong experience with Microsoft. Its software works barely enough to sell to corporations; poor users have to endure it.
And new app developers will have to build upon that. Imagine the next app developer, you have to take into account that a platform pretends to support printers when it is not the case. So you will do things like: if there is a printer api but i don't see a printer available (/connected), pretend that we can't print and don't show the button... and so on...
If you have doubts about that, you can read the history of the openxml format: https://ooxmlisdefectivebydesign.blogspot.com/2007/08/micros...
No, they can't. "The application" has already been written by a 3rd party who could be out of business for all we know.
> If it is does not work, it does not work.
Or you could let it keep working. Somebody might depend on it, and none of this is their fault.
1. the software on your computer
2. the computer's hardware
3. the ethernet cable or wifi
4. the switch
5. the router
6. the cable modem
7. the internet cable to your house
8. the ISP
9. the remote system you're trying to connect to
Nothing has improved for decades.
From MSDN:
> An HRESULT value consists of the following fields:
> A 1-bit code indicating severity, where zero represents success and 1 represents failure.
> A 4-bit reserved value.
> An 11-bit code indicating responsibility for the error or warning, also known as a facility code.
> A 16-bit code describing the error or warning.
The "reserved value" also includes a bit for non-Microsoft code (which driver vendors and other API producers can use, although I don't know how often they do)
There's a list of common "facilities" here: https://learn.microsoft.com/en-us/openspecs/windows_protocol...
As a regular user, you will see errors like 0x8ACEF00D, but if you decode them, you can get a sense of what part of the system ran into the failure. Compared to the "negative value indicates failure, look up the possible failures and what they mean for every function" approach many other APIs follow, that's a welcome change.
Of course there's no guarantee that Microsoft doesn't return some kind of meaningless internal E_SOMETHING_WENT_WRONG value, but for a lot of APIs, there are details hidden in plain sight. It won't tell you your ISP's fiber has snapped, but it'll tell you if the problem is within the driver, a security limitation, an HTTP error, or a generic network stack issue.
Users should not be seeing "08CAE5D012" error messages. They should be seeing "Connection refused. (08CAE5D012)" messages, or "No connection. (83255321)" messages instead.
There's is a non-trivial protocol for translating HRESULTs into error text; but it is often not supported properly by Windows applications, or Windows OS components.
e.g. DirectX which returns generic 0x8004005 E_FAIL errors. Or the WinSock APIs, which return HRESULTs, but don't provide text message translations; or Microsoft Installers that report raw HRESULTs with no attempt to capture the associated error text.
With discipline, the entire system could have worked. But there wasn't, and it didn't.
"Connection Refused No Further Information"
Well thanks pal.
Try: ISP router intercepts DNS and drops the record because your company put private addresses on public DNS and the router has rebind protection.
1. DNS lookup fails because the DNS server address configured at the computer OR router is incorrect 2. DNS lookup fails because the configured DNS server is down 3. DNS lookup fails because some firewall is blocking requests to it 4. DNS lookup fails because the network was congested 5. DNS lookup fails because of interference in the Wifi channel from other Wifi networks 6. Etc. Etc.
So indeed, usually when the network is down you only get "DNS lookup failed". Because the actual reason may be complicated. Of course, usually it is due to your computer not being connected to a router so usually the error message hints at that (in layperson's terms: not connected to the internet). So that's why browsers hint at it being your connection, i.e. ethernet or wifi.
But there is no way to make sure. All we know is that DNS seems to fail. The reason it fails could be any part of its configuration which is spread out across physical systems and software components.
The best we can say: probably your ethernet/wifi connection. If we trust that none of the other components are failing then it must be your connection that is failing.
It's like trying to find out what's wrong in 1+2+32+4=10. Sure, it seems like the 32 should be a 3. But maybe the 10 should be a 39. There is no way to tell anymore. All we can do is make an educated guess.
1. is the printer turned on?
2. is the printer out of paper or ink?
3. does the cable have a loose connection?
4. is it the wrong printer driver?
5. do you need to reboot windows?
6. do you need to power cycle the printer?
No help anywhere. You just try things one by one, sacrifice a small animal, and then maybe the printer gods will smile on you and the printer will print.
This is not swallowing errors. This, in the Linux parlance, is not breaking user-space.
There are two ways to handle the situation presented: error out because the machine can never have printers or return an empty list of printers because the machine can never have printers. They're both valid but only one of them doesn't break user-space.
HTTP 200
{"error": "Not found"}If your choice is between playing whack-a-mole with all parts of the system which might call out, or just issuing a 404 (after all, if there's no Internet, you're not going to find a web page on it), that's a reasonable way to solve the problem.
How is a 404 equivalent to not throwing an error? 404 would be like throwing the error and then not handling the 404.
And the equivalent of hanging and crashing in the xbox example would be hanging and crashing.
Agree this is a good pattern. Im not categorically against throwing errors but I like returning errors or the result and then having to check at the caller. Do this in typescript a lot.
It does actually. This approach ensures that old apps (whose authors never thought it would run on something called an Xbox) will seamlessly run and perform all functions properly except for printing, which isn't supported on Xbox. Panicking here would mean every older app has to update their code to support Xbox.
The article isn't so much about swallowing exceptions, but more about designing the system so you don't have to raise exceptions.
That kind of thing intuitively sounds bad. But I guess of this is in a space shuttle I would take that back.
If the user clicks on a print button in an app on the Xbox, and the app crashes (possibly losing some of the user's data), that's a bad experience for the user. Why do that to them just to stick it to the programmer?
And on top of that, now some developer at some company (often not even the person who initially made the bad assumptions about printing) has now been called in on a weekend by their boss to scramble to fix the issue and push out a new release. Why do that to people?
Regardless, also consider the API contract. If the printing API isn't documented to throw any exceptions, and you start doing that, you're breaking the contract. You can't blame the programmer for not considering platforms without printing support; you've documented that API as always returning something without blowing up.
But that's still irrelevant; don't break user code just because you can, or because it's more expedient for you to do so.
Your Go example is completely unrelated; you're talking about making things blow up at testing time, which I agree is the right thing to do. But that's also when you have full control over the code and the testing. If a third-party platform API starts behaving in ways you didn't expect it to behave, that's a whole other thing.
Are you serious? I thought a key principle of Go was to handle panics in a layered way at runtime
But it'd be way out of scope to put a recover block outside every function call just incase it did a runtime error: index out of range [1] with length . In this way its much different than try catch in languages that explicitly call out what they throw
There's nothing wrong or buggy about the printing API on Xbox returning a list of no printers instead of an exception.
It's essentially the Null Object design pattern. Even Go implements this in the way that a zero list can still be accessed an jsut behaves as an empty list.
Or in unix how /dev/null exists, which implements the file API but doesn't do anything. This is way nicer than fixing every UNIX program to handle stdout not existing sometimes.
Not limited to testing time, though.
This is one thing I like about Go's panic. You're mostly not supposed to use it or recover from it at run time. It serves as a great vehicle to blare loud sirens at testing time that you (the programmer) screwed up (and that's ok, we all do), and it's time to figure out where and how to fix it :)
PS this analogy works in a lot of domains - If you have actors in a system actively trying to hide their flaws/errors it will be exponentially harder to root them out and solve the issues.