Académique Documents
Professionnel Documents
Culture Documents
keyword type has an enumerated (enum) type def inition, most compilers treat enums as signed integers and will not issue a warning if some other compatible value is passed. T his situation implies a need to return some "invalid keyword" error indicator to the caller. Depending on the complexity of an API, the number and type of error that needs to be reported can vary. In this particular case, a simple Boolean indicating success, or f ailure due to an invalid keyword, is suf f icient. In addition to inf luencing the implementation, this API design choice imposes a constraint onto the caller as well. T he client code calling these f unctions is obligated to check the returned result to ensure that no inadvertent error has occurred. T here are, of course, alternative API styles that can perf orm the same task but with dif f erent tradeof f s. For example: void setColor4f(float r, float g, float b, float a); void setColor3f(float r, float g, float b); void setColor4i(int r, int g, int b, int a); void setColor3i(int r, int g, int b); void setColor4fv(float* v); void setColor3fv(float* v); void setColor4iv(int* v); void setColor3iv(int* v); T his API can accomplish the same ef f ect as the previous API, but it goes about it in a dif f erent manner. Previously the f unction name specif ied only the type of value, relying on a keyword parameter to indicate exactly which value is af f ected. Like the previous example, this second API provides parameter type checking at compile time and provides f lexibility to the caller by allowing parameters to be passed individually or as vectors. Its f unction names are somewhat more meaningf ul, though, conveying not only the purpose of the f unction, but also the number and type of arguments expected. T he implementation of each f unction can be small, simple and ef f icient. T he number and type of each argument is f ixed by the API, and thus there is no need f or parameter validation and no need to return any sort of error code. T he f unctions of this API cannot f ail. But the API does require a number of very similar f unctions f or each allowed parameter type and parameter passing style. T he f unctions that accept vector parameters have to assume that the pointer they receive is indeed a pointer to the correct number of elements of the appropriate type. Although the simplicity of the implementation of each f unction is somewhat of f set by the need to provide many such f unctions, this can become an advantage since sometimes a linker can eliminate f unctions that are never called. T he switch/case implementation encouraged by the previous API of f ers the linker less opportunity f or dead code elimination. T here are, of course, circumstances when it is not possible to design an API whose f unctions cannot possibly f ail. Consider the f ollowing example of "pass/f ail" style. bool someFunction(someArguments); if (NO == someFunction(theseArguments)) rslt = getError();
T his is simple and easily tested f or success or f ailure. But it may not be appropriate in some situations since it does not lend itself to f unctional composition such as f (g(x)). T he pass/f ail style of API makes it easy to report that something has gone wrong, but it is not so easy to report precisely what has gone wrong. For that reason it is not uncommon to see this style combined with some variation of a "get last error" mechanism to help determine the actual cause of the error. As an alternative, consider the f ollowing. s omeReturnType someFunction(someArguments); result = someFunction(args); if (NULL == result) handleError(); OR if (result < 0) handleError(); etc. T his style relies on an ability to return distinguished values in order to indicate an error. For example, returning a NULL pointer or a negative number to indicate to the caller that the desired result is not f orthcoming. Functions def ined with this style can be combined, but only with care. For example, f (g(x)) where g(x) might return NULL necessitates that f () checks f or and handles that possibility. Another concern with the use of distinguished values to indicate an error is that there is of ten a greater number of possible error causes than there are available distinguished values of a given type. For example, does a -1 return value indicate "out of memory", "end of input", "disk quote exceeded"? T hus this style of API also suf f ers f rom an inability to clearly indicate the cause of the error, and theref ore is also typically used in conjunction with a "get last error" mechanism. In this current example, handleError() would be responsible f or determining the precise nature of the error and behaving appropriately. T he "get last error" concept is of ten seen in the APIs of popular operating systems, such as Windows and Unix. T he pattern is typically something similar to the f ollowing. clearError(); value = fnT hatCanSetError(someParameters); result = getError(); T his style does not interf ere with the return types or arguments and can be used to determine "what" went wrong. However, the developer must take care since accidental injection of additional calls between the clearError() and the getError() can result in an earlier error being overwritten by a subsequent one. Furthermore, in a multithreaded environment, each thread must maintain its own "last error" variable, and interrupts must be precluded f rom using this error reporting mechanism. One variation of this style is to have the f unction that may set an error clear any previous error on entry, and set a new one if it occurs. T his is a somewhat cumbersome and potentially conf using style, particularly when wrapping system calls on Unix platf orms, where it is not always consistent which system calls return distinguished values and which ones manipulate errno, etc. And it does not eliminate the potential to overwrite an earlier error indicator with a subsequent one. One f inal example concerning error reporting uses an out-parameter to provide the cause of an error, should one occur. someReturnType someFunction(resultType *result, someArguments); Here it is possible to use f unctions as parameters, although doing so precludes checking the result
parameter that such f unctions return. An expression such as f(&f_error, g(&g_error, x)) f orces the caller to evaluate all of the possible out-parameters to determine if an error occurred somewhere within the combination. For consistency, the parameter used f or reporting errors is typically the f irst parameter that it so always f alls in the same position even when used with f unctions accepting variadic arguments. However, it is necessary f or the called f unction to check the parameter f or NULL. Unf ortunately, it can only be assumed that passing NULL f or the error parameter indicates that the caller does not care about errors. T his potential disadvantage is of f set by the f act that this style is thread-saf e and f ully reentrant. It can be used in multi-threaded environments without requiring thread-local storage, and it can be used by f unctions invoked by interrupt handlers as well. In summary, the style used f or a given API inf luences the underlying implementation, potentially af f ecting dead code elimination, and imposes specif ic usage patterns on its clients. When designing an API, it is usef ul to compare the implementation and optimization opportunities af f orded by alternative API styles, as well as the usage model of the client code that invokes them. Understanding APIdeology the interworking of APIs and the constraints that each API places on the implementation and its clients -- allows the designer to produce results that are in alignment with the system design goals.