At 2018-10-31T11:47:20-0400, Paul Winalski wrote:
On 10/31/18, Warren Toomey <wkt(a)tuhs.org>
wrote:
>
> The POSIX file API is a great example, but not of a deep
> interface. Rather, it’s a great example of how code with a very
> complicated interface may look deceptively simple when reduced to C-style
> function signatures.
Indeed. It is my hope that in the coming years software engineers will
decline to describe an interface as "simple" until they've seen how much
logic is required to formally verify it.
For me one of the most important software design principles is that
the simple and most common use cases should be the simplest for the
user to code.
Yes. OpenSSL is an infamous example of design failure in this respect.
However, the term "software design principle" is a bit vague. More
specifically you're identifying a maxim of good programming interface
design.
What's good for a library API is not necessarily what's good for a
system call interface. System calls are not really library functions,
and that is why they have had a different section in the manual from day
one.
open/reda/write/(l)seek/close did not primarily constitute, as I think
they were originally conceived, and API; they were there to expose
_primitives_ of the operating system.
The lateness in coming of the C standard I/O library may have been
something of a problem here; an I/O library is exactly where you want to
press your design principle to the maximum. (On the other hand,
sometimes the design space needs to be explored, and you don't know what
the common cases are going to be because you don't have enough data
points yet. In that case you expose the primitive operations and study
what bubbles up when programmers apply the DRY principle--they are
future standard library calls in disguise.)
But I suspect another problem, even had stdio.h been around in 1972,
would have been the obsessive false economy of "programming close to the
metal". Because the C language did that, and because the system call
interface was there and easy to grab a hold of, tons of application
programmers thought they should follow suit, violating Knuth's principle
about optimization with fervor.
I've always thought that the UNIX file primitives
very elegantly
adhere to this principle. Reading a file in UNIX is a simple
open()/read().../close() sequence. Contrast that with VMS's $QIO or
IBM OS access methods, where the full complexity is not only exposed
in the interface, it must be considered, set up, and controlled by the
user for even the simplest operations. Multibuffered, asynchronous,
interrupt-driven I/O is more complicated (if not downright clumsy) in
UNIX than in VMS or OS/VS, but that's OK, IMO--it shifts the
complexity burden to those doing complex things.
I haven't programmed on VMS or OS/VS, but again this sounds like a
scenario where a standard I/O library should have existed but didn't (or
was inadequate to the demands placed on it--as stdio itself arguably
still is, with a static global errno variable and an original design
that ignored reentrancy entirely, such that we have to consult manuals
to determine which calls are safe for multi-threaded apps or use in a
signal handler).
Context has to be managed somewhere. open()/read()/.../close() only
look simple because a lot of context has been pushed down into the
kernel--the list of flags supported by open() has steadily grown over
the years.
The POSIX openat() system call family is a superior design, though still
saddled with a lot of context. I admit, it is probably more annoying to
use for application programmers who want to use it directly. That's
because they're trying to solve their problems at the wrong level. It
irritates them that they have to keep track of their own contexts (in
this case, a file descriptor). A well-designed library is able to do
this for them, but they want to be close to the metal. And too many of
them will not do the job adequately themselves, so they demand that the
kernel handle it itself. Now the system call interface is even less a
set of primitives, and even more something that's "easy to code to" for
sloppy programmers.
That is one way kernel interfaces, memory requirements, and
task-switching times bloat. How close to the metal does one feel then?
--
Regards,
Branden