that are ready or zero, in which case the call returned because the time-out was reached. With this simple change, the usability problems disappear and, because the caller no longer needs to copy the arguments, the code is far more efficient as well.
There are many other ways to fix the problems with Select() (such as the approach used by epoll()). The point of this example is not to come up with the ultimate version of Select(), but to demonstrate how a small number of minor oversights can quickly add up to create code that is messy, difficult to maintain, error prone, and inefficient. With a slightly better interface to Select(), none of the code I outlined here would be necessary, and I (and probably many other programmers) would have saved considerable time and effort.
figure 2: the doselect() function.
public void doSelect(List checkRead, List checkWrite,
List checkError, int milliseconds)
{
the cost of Poor aPis
The consequences of poor API design are numerous and serious. Poor APIs are difficult to program with and often require additional code to be written, as in the preceding example. If nothing else, this additional code makes programs larger and less efficient because each line of unnecessary code increases working set size and reduces CPU cache hits. Moreover, as in the preceding example, poor design can lead to inherently inefficient code by forcing unnecessary data copies. ( Another popular design flaw—namely, throwing exceptions for expected outcomes—also causes inefficiencies because catching and handling exceptions is almost always slower than testing a return value.)
The effects of poor APIs, however, go far beyond inefficient code: poor APIs are harder to understand and more difficult to work with than good ones. In other words, programmers take longer to write code against poor APIs than against good ones, so poor APIs directly lead to increased development cost. Poor APIs often require not only extra code, but also more complex code that provides more places where bugs can hide. The cost is increased testing effort and increased likelihood for bugs to go undetected until the software is deployed in the field, when the cost of fixing bugs is highest.
Much of software development
ArrayList readCopy; // Copies of the three parameters because ArrayList writeCopy; // Select() clobbers them.
ArrayList errorCopy; if (milliseconds <= 0) {
// Simulate waiting forever. do {
// Make copy of the three lists here...
Select(readCopy, writeCopy, errorCopy, Int32.MaxValue);
} while ((readCopy == null || readCopy.Count == 0) &&
(writeCopy == null || writeCopy.Count == 0) && (errorCopy == null || errorCopy.Count == 0));
} else {
// Deal with non-infinite timeouts. while ((milliseconds > Int32.MaxValue / 1000) &&
(readCopy == null || readCopy.Count == 0) &&
(writeCopy == null || writeCopy.Count == 0) &&
(errorCopy == null || errorCopy.Count == 0)) {
// Make a copy of the three lists here...
Select(readCopy, writeCopy, errorCopy,
( Int32.MaxValue / 1000) 1000); milliseconds -= Int32.MaxValue / 1000;
}
} if ((readCopy == null || readCopy.Count == 0) &&
(writeCopy == null || writeCopy.Count == 0) &&
(errorCopy == null || errorCopy == 0)) {
Select(checkRead, checkWrite, checkError, milliseconds*1000); }
// Copy the three lists back into the original parameters here...
}
is about creating abstractions, and APIs are the visible interfaces to these abstractions. Abstractions reduce complexity because they throw away irrelevant detail and retain only the information that is necessary for a particular job. Abstractions do not exist in isolation; rather, we layer abstractions on top of each other. Application code calls higher-level libraries that, in turn, are often implemented by calling on the services provided by lower-level libraries that, in turn, call on the services provided by the system call interface of an operating system. This hierarchy of abstraction layers is an immensely powerful and useful concept. Without it, software as we know it could not exist because programmers would be completely overwhelmed by complexity.
The lower in the abstraction hierarchy an API defect occurs, the more serious are the consequences. If I mis-design a function in my own code, the only person affected is me, because I am the only caller of the function. If I mis-design a function in one of our project libraries, potentially all of my
colleagues suffer. If I mis-design a function in a widely published library, potentially tens of thousands of programmers suffer.
Of course, end users also suffer. The suffering can take many forms, but the cumulative cost is invariably high. For example, if Microsoft Word contains a bug that causes it to crash occasionally because of a mis-designed API, thousands or hundreds of thousands of end users lose valuable time. Similarly, consider the numerous security holes in countless applications and system software that, ultimately, are caused by unsafe I/O and string manipulation functions in the standard C library (such as scanf() and strcpy()). The effects of these poorly designed APIs are still with us more than 30 years after they were created, and the cumulative cost of the design defects easily runs to many billions of dollars.
Perversely, layering of abstractions is often used to trivialize the impact of a bad API: “It doesn’t matter—we can just write a wrapper to hide the problems.” This argument could not be more wrong because it ignores the
References:
Archives