• Aucun résultat trouvé

Abort, Retry, Fail?

Dans le document The Practice Programming (Page 119-123)

Before I built a wall I'd ask to know What I was walling in or walling out,

4.7 Abort, Retry, Fail?

In the previous chapters we used functions like e p r i n t f and e s t r d u p to handle errors by displaying a message before terminating execution. For example, e p r i n t f behaves like f p r i n t f ( s t d e r r ,

. .

.), but exits the program with an error status after reporting the error. It uses the <stdarg. h> header and the v f p r i n t f library routine to print the arguments represented by the

. . .

in the prototype. The s t d a r g library must be initialized by a call to v a - s t a r t and terminated by va-end. We will use more of this interface in Chapter 9.

# i n c l ude < s t d a r g

.

h>

# i n c l u d e < s t r i n g . h>

# i n c l u d e <errno. h>

/ a e p r i n t f : p r i n t e r r o r message and e x i t a/

v o i d e p r i n t f (char a f m t ,

. .

.)

C

va-1 i s t args;

f f l ush(stdout) ;

i f (progname() ! = NULL)

f p r i n t f C s t d e r r . "%s: ", prognameo);

v a - s t a r t (args, f m t ) ;

v f p r i n t f (s t d e r r , f m t , args) ; va-end(args) ;

i f ( f m t [ O ] != '\0' && f m t [ s t r l e n ( f m t ) - l ] == ' : ' ) f p r i n t f ( s t d e r r , " %s", s t r e r r o r ( e r r n 0 ) ) ; f p r i n t f ( s t d e r r , "\n") ;

e x i t ( 2 ) ; /a conventional v a l u e f o r f a i l e d e x e c u t i o n s/

3

If the format argument ends with a colon, e p r i n t f calls the standard C function s t r e r r o r , which returns a string containing any additional system error information that might be available. We also wrote wepri n t f , similar to e p r i n t f , that displays a warning but does not exit. The p r i n t f - l i k e interface is convenient for building up strings that might be printed or displayed in a dialog box.

Similarly, e s t r d u p tries to make a copy of a string, and exits with a message (via e p r i n t f ) if it runs out of memory:

1 10 INTERFACES CHAPTER 4

/ a estrdup: d u p l i c a t e a s t r i n g , r e p o r t i f e r r o r s/

char aestrdup(char as)

C

char a t ;

t = (char s) m a l l o c ( s t r l e n C s ) + l ) ; i f (t == NULL)

e p r i n t f ("estrdup(\"%. ZOs\") f a i l e d : " , s) ; s t r c p y ( t , s);

r e t u r n t ;

3

and emall oc provides a similar service for calls to ma1 1 oc:

/* emalloc: m a l l o c and r e p o r t i f e r r o r a/

v o i d semal l o c ( s i ze-t n)

C

v o i d sp;

p = malloc(n);

i f (p == NULL)

e p r i n t f (" malloc o f %u b y t e s f a i l e d : " , n) ; r e t u r n p;

3

A matching header file called e p r i n t f . h declares these functions:

/* e p r i n t f . h : e r r o r wrapper f u n c t i o n s a/

e x t e r n v o i d e p r i n t f ( c h a r n ,

. .

.);

e x t e r n v o i d w e p r i n t f ( c h a r a , ...);

e x t e r n char aestrdup(char a);

e x t e r n v o i d nemal l o c ( s i ze-t) ;

e x t e r n v o i d n e r e a l l o c ( v o i d a, s i z e - t ) ; e x t e r n char aprogname(void) ;

e x t e r n v o i d setprogname(char a);

This header is included in any file that calls one of the error functions. Each error message also includes the name of the program if it has been set by the caller: this is set and retrieved by the trivial functions setprogname and progname, declared in the header file and defined in the source file with e p r i n t f :

s t a t i c char *name = NULL; /* program name f o r messages a / / s setprogname: s e t s t o r e d name o f program s/

v o i d setprogname(char a s t r )

C

name = e s t r d u p ( s t r ) ;

3

/a progname: r e t u r n s t o r e d name o f program s/

char *progname(voi d) {

r e t u r n name;

3

SECTION 4.7 ABORT. RETRY. FAIL? 1 1 1

Typical usage looks like this:

i n t main(int a r g c , char *argv[])

C

setprogname("markov");

. ..

f = fopen(argv[i] , " r"):

i f (f == NULL)

epri n t f ("can't open %s:", argvri]) ;

which prints output like this:

markov: c a n ' t open psalm.txt: No such f i l e o r d i r e c t o r y

We find these wrapper functions convenient for our own programming, since they unify error handling and their very existence encourages us to catch errors instead of ignoring them. There is nothing special about our design, however. and you might prefer some variant for your own programs.

Suppose that rather than writing functions for our own use, we are creating a library for others to use in their programs. What should a function in that library do if an unrecoverable error occurs? The functions we wrote earlier in this chapter display a message and die. This is acceptable behavior for many programs, especially small stand-alone tools and applications. For other programs. however, quitting is wrong since it prevents the rest of the program from attempting any recovery; for instance, a word processor must recover from errors so it does not lose the document that you are typing. In some situations a library routine should not even display a message. since the program may be running in an environment where a message will interfere with displayed data or disappear without a trace. A useful alternative is to record diagnos- tic output in an explicit "log file," where it can be monitored independently.

Detect errors at a low level, handle them at a high level. As a general principle, errors should be detected at as low a level as possible, but handled at a high level. In most cases, the caller should determine how to handle an error, not the callee. Library routines can help in this by failing gracefully; that reasoning led us to return NULL for a non-existent field rather than aborting. Similarly, csvgetl i ne returns NULL no mat- ter how many times it is called after the first end of file.

Appropriate return values are not always obvious. as we saw in the earlier discus- sion about what csvgetl i ne should return. We want to return as much useful infor- mation as possible, but in a form that is easy for the rest of the program to use. In C, C++ and Java, that means returning something as the function value. and perhaps other values through reference (pointer) arguments. Many library functions rely on the ability to distinguish normal values from error values. Input functions like getchar return a char for valid data, and some non-char value like EOF for end of file or error.

1 12 INTERFACES CHAPTER 4

This mechanism doesn't work if the function's legal return values take up all pos- sible values. For example a mathematical function like log can return any floating- point number. In IEEE floating point, a special value called NaN ("not a number") indicates an error and can be returned as an error signal.

Some languages, such as Per1 and Tcl, provide a low-cost way to group two or more values into a tuple. In such languages, a function value and any error state can be easily returned together. The C++ STL provides a pai r data type that can also be used in this way.

It is desirable to distinguish various exceptional values like end of file and error states if possible, rather than lumping them together into a single value. If the values can't readily be separated, another option is to return a single "exception" value and provide another function that returns more detail about the last error.

This is the approach used in Unix and in the C standard library, where many sys- tem calls and library functions return -1 but also set a global variable called errno that encodes the specific error; s t r e r r o r returns a string associated with the error number. On our system, this program:

#i ncl ude < s t d i o. h>

#include < s t r i ng. h>

#include <er rno. h>

#include <math. h>

/a errno main: t e s t errno a/

i n t mai n (voi d)

C

double f;

errno = 0 ; /* c l e a r e r r o r s t a t e a/

f = log(-l.23);

printf("%f %d %s\nM, f , errno, s t r e r r o r ( e r r n 0 ) ) ; return 0 ;

3

prints

nanOxlOOOOOOO 33 Domain e r r o r

As shown, errno must be cleared first; then if an error occurs, errno will be set to a non-zero value.

Use exceptions only for exceptional situations. Some languages provide exceptions to catch unusual situations and recover from them; they provide an alternate flow of control when something bad happens. Exceptions should not be used for handling expected return values. Reading from a file will eventually produce an end of file;

this should be handled with a return value, not by an exception.

In Java, one writes

SECTION 4.8 USER INTERFACES 1 13

S t r i n g fname = "someFi 1 eName" ; t r y

C

FileInputStream i n = new FileInputStream(fname) ; i n t c;

while ((c = i n . read()) != -1) System.out.print((char) c);

i n . c l o s e ( ) ;

} catch (Fi 1 eNotFoundException e) {

System.err.println(fname

+

" not found");

) catch (IOException e) {

System.err. println("I0Exception: "

+

e);

e . p r i ntStackTrace0 ;

1

The loop reads characters until end of file, an expected event that is signaled by a return value of -1 from read. If the file can't be opened, that raises an exception, however, rather than setting the input stream to nu1 1 as would be done in C or C++.

Finally, if some other 110 error happens in the t r y block, it is also exceptional, and it is caught by the IOExcepti on clause.

Exceptions are often overused. Because they distort the flow of control, they can lead to convoluted constructions that are prone to bugs. It is hardly exceptional to fail to open a file; generating an exception in this case strikes us as over-engineering.

Exceptions are best reserved for truly unexpected events, such as file systems filling up or floating-point errors.

For C programs, the pair of functions setjmp and longjmp provide a much lower-level service upon which an exception mechanism can be built, but they are sufficiently arcane that we won't go into them here.

What about recovery of resources when an error occurs? Should a library attempt a recovery when something goes wrong? Not usually, but it might do a service by making sure that it leaves information in as clean and harmless a state as possible.

Certainly unused storage should be reclaimed. If variables might be still accessible, they should be set to sensible values. A common source of bugs is trying to use a pointer that points to freed storage. If error-handling code sets pointers to zero after freeing what they point to, this won't go undetected. The reset function in the sec- ond version of the CSV library was an attempt to address these issues. In general, aim to keep the library usable after an error has occurred.

Dans le document The Practice Programming (Page 119-123)