by ninetyninenine | 2025-11-01 15:52:08+00:00
First, your categories are not universal. They depend on the language and the runtime. 1. Divide by zero In IEEE floating point you can define it to yield Infinity or NaN and keep going. In some languages it raises an exception. In C with integers you get undefined behavior with no exception at all. If your claim were about a fundamental flaw of code itself, the result would not change across environments. It does. So your category is a policy choice, not a law of nature. 2. Out of bounds In Java you get an IndexOutOfBoundsException. In C you can trample memory and never raise anything. Same code pattern, different outcome. Again this shows your supposed bright line is just a design decision. 3. methodThatDoesNotExist In a statically typed language this is a compile time error and never throws at runtime. In a dynamic language it can throw NoSuchMethod at runtime. Same text, different category, based on tooling. That is not a fundamental metaphysics of exceptions. It is the compiler and runtime drawing the line.

Conclusion from 1 through 3 Your definition of exception as a fundamental flaw collapses the moment we cross a language boundary. What you are calling fundamental is actually configurable convention.

⸻

Second, your machine versus human distinction is self defeating.

You say the machine can determine exceptions but only humans can determine bugs. The machine does not determine anything. It executes rules we wrote. If a rule maps a state to raise an exception, the machine raises one. Whether that state is a bug is defined by the spec. If the spec says division by zero must never occur in production, any runtime that hits it is a bug. If the spec says handle it as Infinity, then hitting it is not a bug. The label follows intent and contract, not the instruction pointer.

Obvious example A payment service has a rule that user input must be validated before division. A request slips through with divisor zero and the service throws. That is a bug even if the language designer felt very proud of raising an ArithmeticException. Flip the rule. If the service is specified to accept zero and return a sentinel value, then raising an exception is itself the bug.

⸻

Third, the data structure versus concept move does not rescue the argument.

Yes, we all know you can throw an object on purpose. Yes, we all know many libraries create rich exception objects. None of that changes the core point. In a nontrivial system, unhandled exceptions or exceptions raised in states that the spec forbids are bugs by definition. The exception is the transport. The bug is the violation that made the transport fire.

Your own toy example proves it You show an inverted conditional that throws. You say that is not an exception in the conceptual sense. But the system would still page the on call, still fail the request, still violate the acceptance test. Every operator and every customer would call that a bug. You are trying to win by relabeling the alarm as not part of the fire drill.

⸻

Fourth, here are obvious scenarios that make the logic clear to anyone.

Elevator Spec: doors must never open between floors. Code hits that state and trips a safety exception. If that ever happens in production, it is a bug. If the safety system decides to log and keep moving instead, and the spec requires a stop, then not throwing is the bug. The exception path is just the messenger.

Bank transfers Spec: never double charge. Off by one posts the same transfer twice. The code throws on the second insert due to a unique constraint. If it throws, the incident is a bug. If it does not throw because the database had no constraint and you silently post two charges, that outcome is a bug. In both cases the presence or absence of an exception is not the category. The spec is the category. The bug is the deviation.

Medical device Spec: sensor readings out of range must be clamped and logged, not crash the loop. If the runtime raises an exception and the loop dies, that exception is the manifestation of a bug. If the runtime never raises and you keep using garbage without logging, that silent path is the bug. Same state space. Different handling. The spec decides which path is the failure.

Toaster Spec: pop after two minutes. The timer arithmetic overflows and you get a runtime exception in the control loop. That exception is how the bug announced itself. In a different implementation the overflow wraps and the toaster never pops. No exception at all. Still a bug.

These are obvious because everyone can see that what matters is the contract. The exception is only a vehicle for discovery.

⸻

Fifth, the correct taxonomy already exists and it does not match your story.

Industry standards separate three things. A fault is the cause in the code or design. An error is the incorrect internal state. A failure is the externally visible deviation. Exceptions are one mechanism for surfacing an error or failure. When an exception causes a failure relative to the spec, we have a bug. When an exception is expected and fully handled within the spec, we do not. This mapping is stable across languages in a way your concept versus data structure split is not.

⸻

Final point

Your thesis needs both of these to be true at once.

A) Exceptions mark fundamental flaws independent of intent. B) Bugs are only deviations from intent.

But we have shown counterexamples where intent flips the classification and where language choice flips the behavior. That means A and B cannot both stand. Once A falls, your whole distinction reduces to wordplay. Once B stands, the only consistent rule is this:

If an exception leads the program to violate its requirements, it is evidence of a bug. If it does not, it is routine control flow. In practice many exceptions are how bugs announce themselves. That is why production teams triage exception rates and not treatises on metaphysics.