Software Does Not Contain Errors 2021-08-21 Tuukka Pensala ABSTRACT We show that software and programming languages can not consistently implement error semantics, meaning that pieces of software can not consistently report errors to their users. Consequently so-called error handling mechanisms of many common programming languages are in fact not error handling mechanisms, but arguably more or less misplaced complications arising from a misconception of what an error is. We argue that an extensive misunderstanding of the nature of errors in the field of computing has enabled software quality to degrade and ethical issues to form. With a clear understanding of an error we define what is good software. PRAXEOLOGY AS THE FOUNDATION OF ANALYSIS In accordance with the distinctive methodology of the Austrian School, praxeology, we note that running software is just an extension to purposeful behavior. Software is a means to an end, a tool to reach some goal for whoever runs the software. Any material process or a result is not in itself correct or erroneous, or a success or a failure. An error or a failure exists only in relation to some purpose. Anything that software reports as an error can only be an error in relation to the purpose of someone who acts. Different users at different times may have different purposes and thus different criteria for what software behavior is erroneous for them. A specific behavior of software can be erroneous in one purpose and successful in another. WELL-DEFINED ERRORS The purpose of the programmer exists at the time of writing the software, and so errors in respect to that purpose are well-defined in a contained manner. A bug is an error relative to the programmer's purpose in writing the software. An error relative to programmer's purpose that is caused by the software not being executed as described in the source code is a compiler or a hardware bug. These well-defined error classes are categorically different from errors relative to the purpose of an arbitrary future software user, which is the focus of this article. INTENT BEHIND SOFTWARE ERROR SEMANTICS The C fopen can be used for at least these three distinct purposes: 1) to open a file with the specified name in a specific mode, 2) to test if a file of specified name is openable in a specific mode, and 3) to ensure that a file of specific name is not openable in a specific mode. In purpose 1 the failure modes are those which do not open the file. In purpose 2 there is no failure mode. In purpose 3, opening the file is the failure mode. However, the name of the procedure has been chosen to reflect purpose 1 and the error semantics chosen for the procedure are such that behaviors that contradict the purpose reflected in the name are the failure modes. This seems natural, because both the most versatile and the most common usage of the procedure arguably is the behavior of opening a file and giving access to it. It is useful to name a procedure in a way that helps to recall and understand what the internal structure of the procedure is, i.e. what are all the things it can do. But the name or an intent implied in the name does not give the procedure its internal structure, which is what in reality restricts the possible well-defined uses. So, in some cases a developer may have the idea of a usual purpose, or more correctly a predicted usual purpose for the software he is creating, and then decide which behavior is an error in respect to that purpose. But this purpose may or may not be the actual purpose that the user of the software will later have, which is in general impossible to know ahead of time. The decision of what is and isn't an error in this conception is an empirical prediction about future human choices regarding the usage of the software. Because future human choices are not perfectly known to the developer and software can not read the mind of the user, the best guess for the future usage of the software made by him can not be correct all of the time in general. If the prediction of the programmer was good, then the reported error is actually indicative of an error most of the time, but occasionally the behavior that is reported as an error is the desired behavior for the user, meaning that the prediction has been falsified. Now "error" has lost conceptual soundness for the user of the software. "Error" now at best means: usually a failure, but sometimes a success. To still maintain that it is consistent for software to have error semantics, one can only resort to the position that someone decides a winning purpose that is the yardstick for which behavior of software is erroneous. Instead of errorness being dependent on a futile empirical prediction about human action, could it be argued that someone decides a valid standard yardstick of purpose, and that others need to submit to it? There is nothing in reality or reason that would necessitate a preference of one yardstick over another. Everyone can choose their own purpose and in every acting moment they do. So a purpose of the developer elevated over the purpose of the user in some absolute importance would be solipsistic fiction by the developer, and besides muddling the concept of an error, it would also rudely belittle the user. Imagine some kind of a smart hammer reporting to you that a failure has happened when in fact you were just hitting something other than nails with it. Furthermore, what practical value is there for the future user of a reusable piece of software to have access to the past prediction or opinion of the developer in the form of error semantics? The user already knows his own purpose. If the user also already understands what the software is capable of performing, then the user gains nothing from also knowing about the past mental state of the developer. In fact such additional information is only a hindrance, because mental capacity is limited. So neither a prediction of a usual purpose nor a specific purpose set in stone by whatever metric can consistently determine what software behavior is an error for the user. SOFTWARE DOES NOT HAVE INHERENT PURPOSE It is an interesting fact of reality that software can not in general decide whether using it succeeded or failed. A piece of software is a fixed structure that may be employed for different purposes, but it itself can not decide the specific purpose. Errors exist only in the interaction of humans and software. A software that "doesn't work" is software that doesn't fulfill the purpose of the user. An error can manifest only in reaction to purposeful action. It is not so much that the acting man is prone to error, but that nothing else than those who act can err. Labels can be informative about some usual purposes for which a piece of software is particularly useful, but labels don't determine the internal logical structure of software, which is what gives software its usefulness as a means of action. It is bad engineering practice to read so much into labels that the understanding of the internal structure of the work of engineering becomes distorted. In a very fundamental sense the execution of software is independent of the choice for labels, and the purpose is up to the user. A label on a box can be a helpful indicator of what's inside, but it doesn't define the contents or the purpose of future action utilizing the contents. Labels do not define the behavior or purpose of software. ISSUES WITH MODERN SOFTWARE One may agree with the above, but still think that it is unimportant pedantry. Does it really matter that much if "software errors" are not conceptually errors, but only "practically"? After all, programming is a pragmatic profession and maybe it's good enough that things called errors are meaningfully errors only 99% of the time. Calling a thing a not-thing is a misapplication of a word and it easily distorts one's thinking. It would be strictly irrational to think that one's success can at the same time be his failure, however infrequent that would be. It would be akin to calling a hexadecimal number a decimal number, and justifying that with the fact that most of the digits are the same in decimal numbers. It would be akin to stating "data structure", but occasionally meaning "procedure". Ironically it would be erroneous in the correct sense of the word. Now that we have clarified the meaning of errors, it becomes apparent that modern software has become increasingly erroneous. For example, when Windows unexpectedly restarts to update, the purpose of the surprised user, who wanted to get some work done in some specific timeframe, was prevented from realizing. An error occurred. The fact that this event is not widely recognized as an actual error gives reason to believe that an extensive misconception of errors in computing has contributed to the degradation of software quality. How could developers reduce errors that they do not even recognize as errors? It gets worse. The errors of modern computing are starting to get spinned to be a good thing by those who cause them. For example, the above failure in one's purpose that is caused by an unexpected interruption is dictated by Microsoft to be "a countdown to goodness". They should be apologizing for the interruption! Instead it appears like they believe that their purpose regarding the use of software is justly superior to the user's purpose regarding the use of the same software. It appears like they believe that they control the supreme yardstick which decides the correct purpose for a piece of software. This is strictly false, because it is an axiomatic fact that every individual chooses his own purpose. One can not do otherwise while maintaining consciousness. The attempt to influence the user to have a positive attitude towards the failure of his own action is literally encouragement to self-erasure. The misconception is allowing actual ethical issues to form! THE SOLUTION The program itself should be written to consider the error condition as actually just a condition that it handles. - Casey Muratori It is now apparent that what software reports as an error is not consistently an error and it can be harmful to continue thinking that it is. But what is the correct concept then? There may be no single concept that could encompass all or even most of the so-called errors in current usages of the word in software. Any such concept would essentially be the concept of an error, and such a concept can not be defined without also defining the users of the software. But one can still look at specific usages of the word "error" and try to find a correct replacement concept in each specific case. As we saw with fopen, it seems natural and helpful to label a procedure with purpose-indicating words that in some sense convey the full potential of the procedure. With simple enough procedures the notion of full potential is a meaningful thing. For example, if a procedure consists of operations performed in sequential manner, then returning from the procedure early corresponds to partial execution -- execution which is stopped before doing all possible steps of it. This is well-defined without defining the user of the software. In this notion a procedure returning early can be often contrary to the purpose indicated in a helpful name, and would be easily miscategorized as an error. In C, a procedure can set the global errno value if an "error" happens. This specific usage of the word "error" seems to correspond well with an early return. The C errno is conceptually more like an early_return_reason, a partial_exec_reason, or a stop_reason, where the different possible values of it indicate what behavior, instead of all of it, happened and why. In this sense even EWOULDBLOCK is consistently meaningful, when in the error sense it is often nonsensical. But complex software does not consist only of sequential pieces, so therefore much of complex software behavior can not be meaningfully divided into the two categories of early and late returns. The dichotomy does not apply to complex software. Furthermore, in complex software a failure mode of a usual purpose of the user may not even be explicit in the source code, but be an emergent property of the software system. When playing a video game, a surprising freeze caused by a stop-the-world garbage collection may easily conflict with the purpose of the software user, and thus can be as much an error as the error of not opening a file when the purpose was to open it, or the error of opening a file when the purpose was to not open it. To have software to convey useful meaning to all users with correct words in all usages, it must never attempt to report an error, but instead simply report what happened and why. If one writes pieces of software that don't attach the subjective binary status of errorness to any result or report, then all of the pieces will be reusable for any purpose that they work for while also maintaining conceptual correctness. Consequently, as the developer does not have a misplaced conception of an error in mind, recognizing actual errors can become easier. THE PROBLEM OF ERROR HANDLING MECHANISMS In trying to maintain that the concept of an error exists independent of a user, one also opens the door for programming language designers to introduce dedicated error handling mechanisms to programming languages. By pretending that the concept of an error is well-defined within software, like the concepts of a procedure or a data structure are, it is possible to start pushing for pervasive unified mechanisms to make the handling of "errors" easier. But because an error is ill-defined outside of a specific use-case, then also error handling mechanisms are ill-defined outside of a specific use-case. Therefore all error handling mechanisms of programming languages are conceptually not error handling mechanics, but rather mechanics of where the responses to predicted usually unwanted behaviors are syntactically separated from the responses to other behaviors, often implemented in such a manner that the programmer can not reasonably avoid such mechanism when programming in the language. By reasoning which behavior to report as an error, as the language designer guides, one spends time on deciding whether a meaningful report should in addition indicate a subjective quality of errorness. A report that is always correct in its meaning is more useful than a report that can be incorrect in its meaning, so thinking about the errorness of a behavior is a waste of time. If such deliberation can not be easily avoided but the dedicated mechanism for "error handling" is pervasive, then the programmer of that language is forced to waste time. However, what is often mixed with that thought, is the consideration of what the usage of a specific "error handling mechanism" accomplishes compared to other language constructs. "If I decide that this well-defined behavior has the quality of errorness to it, what consequences will there be for the program control flow? Is the control flow useful or not in this situation?" But this consideration of what construct to use is orthogonal to the subjective classification of errorness. It is just ordinary programming, thinking about which language constructs are the most useful in what one is trying to achieve. C++ EXCEPTIONS Exceptions, when done right, separate the happy path from the error path. - isocpp.org FAQ With the gained conceptual clarity on the subjective nature of errors, the above statement is obviously absurd. There is no right way to classify what is a happy and what is a sad execution path in software. The exceptions of C++ are not strictly about erroneous, exceptional or sad situations as those can not be defined without also defining the future users and the future environments of the software. Exceptions are about unwinding the call stack to some level with type-based configuration and jumping to a destination location where a value sent from the throw site becomes accessible. Or something like that. "A funky unwind-jump-return mechanism with type-based configuration" would be an infinitely more correct characterization of exceptions than "an error-handling mechanism". If Stroustrup was thinking back then in terms of a funky unwind-jump-return mechanism with type-based configuration, then maybe C++ would've taken a different path. Maybe C++ programmers now would not be spending significant amounts of time bending their mind and code to fulfill the constraints of exception safety. Maybe C++ programmers would not waste time in solving arbitrary decision problems by internally debating if some behavior is exceptional enough or unhappy enough to warrant the complications coming from using the funky unwind-jump-return mechanism with type-based configuration. A significant amount of mental and practical friction has been enabled by the pretence that an unsound concept is sound. C++ is a lie! The existence of friction in using C++ exceptions seems to be accepted among C++ experts, but a profound conceptual confusement is rarely seen as a factor. See for example "Zero-overhead deterministic exceptions: Throwing values" by Herb Sutter. There the deficiencies of C++ exceptions that keep them from having the "ideal error handling characteristics" (p. 13) are viewed as practical obstacles that can all be worked around. On page 2 there's a definition of an error as sort of a reminder about what are the situations that a unified "error handling" system should handle: error: "an act that ... fails to achieve what should be done." [Merriam-Webster] Exactly! For there to be an error there must be an ought. An error exists in relation to prescription, not description. Oblivious to this, the C++ experts are unknowingly hard at work in trying to solve the perennial is-ought problem raised by David Hume. "Normal" vs. "error" is a fundamental semantic distinction, and probably the most important distinction in any programming language even though this is commonly underappreciated. Therefore, the distinction should be surfaced explicitly (though as elegantly as possible) in language syntax and program structure. - Herb Sutter, Zero-overhead deterministic exceptions: Throwing values, p. 13 From what a piece of software could do, it can not be deduced what the piece of software ought to do, but the C++ experts are determined to change that. Once you start seeing their attempts at defining absolute norms, you can't unsee it. They believe that some day a new version of C++ exceptions could emerge that can be correctly and consistently used to separate normal from the abnormal. On that day moral philosophy would be revolutionized. They are building technology on imaginary foundations. They keep trying to stitch an invalid concept into a valid one, but when one patch is applied, another seam always ruptures. Admittedly, not all of the ruptures are caused solely by the conceptual misunderstanding, but also by the multitude of design constraints that the C++ language designers have imposed on themselves over time. Regardless of any "historical baggage", the act of creating an "error handling mechanism" can be only as successful as the act of creating a square circle. It is simply impossible to create a tool that can be used to decide absolute norms, however elaborate a tool one can devise and however mundane the norms would be. If C++ designers keep being fooled by purpose-indicating labels, their ball of mud will just keep growing as they are particularly persistent in their task of trying to fix the unfixable, make sense from nonsense and derive truth from falsehood. To sum up, if software and its parts need to decide whether they succeeded or failed, the developer will eventually need to engage in counterproductive activity, the user will eventually be faced with absurd usage of the concept of an error, and the user will also face errors which the software does not recognize as errors. Programming language designers can not include the concept of an error in language semantics and they haven't. They are just calling some language feature with the wrong name. They have erred from the path of wisdom by not calling things by their proper name. The divergence from reason has accumulated and allowed influential software makers to start brainwashing their users to like actual errors, encouraging users to self-erase. It is a big deal. THE DEFINITION OF GOOD SOFTWARE The most important property of a program is whether it accomplishes the intention of its user. - C.A.R. Hoare Perfect software is such that no errors occur in its usage. Useless software is such that it can not be used for any purpose -- there will always be an error that prevents one from successfully using it. These two idealized extremes determine the scale of software quality in a way that takes into account both the human aspect and the empirical aspect of software engineering. Good software is such that almost all of the time the user reaches his goal in using it. It first allows the user to easily recognize different purposes for which the software is useful, and then it is successfully used for such a purpose. Things like bugs, crashes, slowness, latency, unwelcome surprises, ads, large resource usage, frequent user-blocking updates and lies or ambiguities about the features of the software all contribute to the possibility of an error occurring in the usage of it, and thus make the software worse as a means of action. "Good software" is "good" as in "a good tool".