A Philosophy of Software Design

H. S. Teoh hsteoh at qfbox.info
Sun Jun 28 03:30:05 UTC 2026


On Sat, Jun 27, 2026 at 07:35:23PM -0700, Walter Bright via Digitalmars-d wrote:
> On 6/27/2026 12:44 PM, H. S. Teoh wrote:
[...]
> > E.g., sqrt() is involved in some calculation that eventually ends up
> > as the dimensions of some on-screen object.  Now your rendering code
> > has to deal with the problem of how to draw an object with NaN
> > dimensions.  (Complexity added: an extra NaN check in render() to
> > prevent it from going off the rails when it tries to loop from 0 to
> > NaN.  Or extra NaN checks somewhere in-between to prevent it from
> > getting into the render queue and reaching the render function in
> > the first place. Either way you haven't eliminated complexity,
> > merely shoved it into a different form which has to be handled
> > elsewhere.)
> 
> I prefer errors to be obvious rather than papered over.

One could also argue that it's preferable for the error path to be
obvious, rather than implicit in the propagation of NaNs through code
paths that handle non-error computation.

For example, you get a NaN result, now you have to find the bug.  Where
did the NaN come from?  Nobody knows, it could be anywhere from the
first computation that might produce a NaN, to the end of the program
where the NaN is observed.  Not useful for actually finding and fixing
the bug.  You have to spend time to locate the point of failure.

Whereas with an exception, you immediately know exactly where the point
of failure is: the exception carries with it file and and line number
information.


> > Sometimes throwing an exception *is* the least complex way to deal
> > with the problem.  In opening a file, for example, instead of
> > throwing an exception, you *could* return a dummy object in place of
> > a file if the file doesn't exist, or the filesystem is corrupt, or
> > some other error occurs.  Yes you got rid of a throw statement, but
> > at what cost?  Now downstream code has to deal with a dummy file
> > object.  Does it continue processing it as a normal file of 0 size?
> > What if the program needs to differentiate between "file not found"
> > vs. "filesystem is corrupt"?  What if an empty file is valid input,
> > and you need to differentiate that from an error condition?
> > Somewhere down the line, *somebody* has to deal with the exceptional
> > condition somehow.  A single throw statement is less complexity than
> > a bunch of special code to differentiate between an empty file and a
> > dummy file representing an error state.
> 
> A reasonable question. Consider:
> 
> ```d
> err = fprintf(stdout,"hello ");
> if (err)
>     handleIt();
> err = fprintf(stdout,"betty\n");
> if (err)
>     handleIt();
> ```
> as opposed to:
> ```d
> fprintf(stdout,"hello ");
> fprintf(stdout,"betty\n");
> if (stdout.isError)
>     handleIt();
> ```
> Which would you prefer?

I prefer an exception-based mechanism:

```d
try {
	writeln("hello");
	writeln("betty");
	...
} catch (Exception e) {
	handleIt();
}
```

Not much different from your 2nd variant, with the advantage that the
Exception object tells you exactly where the problem happened, whereas
stdout.isError leaves you guessing, where did the problem happen?
Nobody knows, you have to do detective work before you can start fixing
the problem.


> (The downstream code shouldn't have to deal with a dummy file object.
> It should just deal with the file object as if it succeeded. The file
> object in an error state would simply do nothing.)

That's what I was getting at.  So you have code that continues running
as if the file is not in an error state.  Like floating-point
calculations that absorb a NaN and continue computation as if nothing
was wrong, only each time NaN propagates to the result.  You don't know
until the end that a problem has occurred earlier.  In the case of NaNs,
it's not so bad because a NaN in the output is an obvious indicator of a
problem.  With a file in an error state, the program may continue
running in ignorance of the problem until the end, and if the programmer
was careless the program might even declare success, only when you look
at the output file it's empty (because the file object was in an error
state so all operations were no-ops).  And now you're scratching your
head as to why the program produced an empty file instead of the
expected output.


> I know I'm pushing upstream with this notion. I know it is not the
> conventional wisdom. But it has a lot of potential to significantly
> simplify code and eliminate a lot of untested error paths. It is worth
> looking into.

I'm not saying that your idea has no merit.  My point is that it
depends.  For some cases, yes, having a NaN (or equivalent) to poison
the computations help you simplify your code.  I wouldn't want to have
to check for an error state after every single floating-point operation,
for example; that would be unreasonably onerous.  But in other
situations, like opening an output file, I *do* want to know immediately
if there's a problem, instead of having the program blindly running all
the way to the end and perhaps even deciding that output was produced
successfully, only for the user to discover afterwards that the output
file is empty or missing.


T

-- 
"Computer Science is no more about computers than astronomy is about telescopes." -- E.W. Dijkstra


More information about the Digitalmars-d mailing list