Incorrect practice of outputting error messages to a function with one input and one output

I was once told that functions with one input and one output (not exactly one) should not print messages when called. But I do not understand. Is it for safety or just for convention?

Let me give you an example. How to deal with an attempt to access data in a sequential list with the wrong index?

// 1. Give out the error message inside the function directly. DataType GetData(seqList *L, int index) { if (index < 0 || index >= L->length) { printf("Error: Access beyond bounds of list.\n"); // exit(EXIT_FAILURE); } return L->data[index]; } // 2. Return a value or use a global variable(like errno) that // indicates whether the function performs successfully. StateType GetData(seqList *L, int index, int *data) { if (index < 0 || index >= L->length) { return ERROR; } *data = L->data[index]; return OK; } 
+4
source share
6 answers

I think there are two things here:

  • Any visible and unexpected side effects, such as writing to streams, are usually bad, and not just for functions with one input and one output. If I used the list library and started quietly writing error messages to the same output stream that I used for my normal output, I would consider this problem as a problem. However, if you write such a function for your personal use, and you know in advance that the action you want to do should always print a message and exit() , then this is normal. Just don't force this behavior on everyone else.

  • This is a specific case of the general problem of informing subscribers about errors. For a long time, the function cannot know the correct answer to the error, because it does not have the context that the caller does. Take malloc() for example. The vast majority of the time that malloc() fails, I just want to stop working, but one day I may want to consciously fill up the memory by calling malloc() until it works, and then proceed with doing something else. In this case, I don’t want the function to decide whether to stop it or not, I just want it to tell me about it, and then pass it back to me.

There are many different approaches to handling errors in library functions:

  • Terminate - great if you are writing a program yourself, but bad for a universal library function. In general, for a library function, you want the caller to decide what to do in case of an error, so the role of the function is limited to reporting the error to the caller.

  • Returns the error value - sometimes OK, but sometimes there is no valid error value. atoi() is a good example - all the possible values ​​that it returns can be valid translations of the input string. No matter what you return on error, whether it be 0 , -1 or something else, there is no way to distinguish the error from a valid result, and that is why you get undefined behavior if it occurs alone. It is also semantically questionable from some purist point of view - for example, a function that returns the square root of a number is one thing, and a function that sometimes returns the square root of a number but sometimes returns an error code than the square root is another. You may lose the self-documenting simplicity of a function when the return values ​​serve two completely separate purposes.

  • Leave the program in an error state, for example, install errno . You still have the main problem: if there is no valid return value, the function still cannot tell you that an error has occurred. You can set errno to 0 in advance and check it every time, but this is a lot of work, and it may just be impossible when you start to engage concurrency.

  • Calling the error handling function is basically just passing the dollar, since the error function should also solve the problems listed above, but at least you can provide your own. In addition, as R. points out in the comments below, except in very simple cases, such as “always interrupt any error”, he can specify too many one global error handling function to be able to intelligently handle any errors that may occur in the way your program can resume normal execution. The technical ability to have numerous error handling functions and transfer the corresponding individual functions to each library, but this is hardly an optimal solution. Using error handling functions in this way can also be difficult or even impossible to use correctly in the presence of concurrency.

  • Pass an argument that will be changed by the function if it encounters an error. It is technically feasible, but not very desirable, to add an additional parameter for this purpose to every library function ever written.

  • Throw an exception - your language must support them in order to do this, and they come with all the ensuing complexities, including fuzzy structure and program flow, more complex code, etc. Some people - I'm not one of them - consider exceptions the moral equivalent of longjmp() .

All possible paths have their drawbacks and advantages, since humanity has not yet discovered the ideal way to report errors from library functions.

+6
source

In general, you need to make sure that you have a consistent and consistent error handling strategy, which means that you want to pass the error to a higher level or process it at the level that it originally occurs. This solution has nothing to do with how many inputs and outputs a function has.

In a deeply embedded system, where a failure in memory allocation occurs at a critical moment, for example, there is no point transmitting this backup (and indeed you cannot), all you can do is enter to provide a watchdog timer reset you. In this case, it makes no sense to reserve invalid return values ​​to indicate an error, or even even check the return value at all if it does not make sense to do so. (Note. I am not a supporter, just lazily, without bothering to check the returned values, this is a completely different matter).

On the other hand, in a beautiful, beautiful graphical application, you probably want to fail as gracefully as possible, and skip the error to the point where it can either be processed / repeated or something suitable; or it appears to the user as an error if nothing can be done.

+2
source

Yes, this is bad practice; Even worse, you are sending output to stdout , not to stderr . This can lead to data corruption by mixing the error message with the output.

Personally, I am very sure that such "error handling" is harmful. You cannot confirm that the caller has passed a valid value for L , so the validation of index is inconsistent. The documented interface contract for the function should simply be that L should be a valid pointer to an object of the correct type, and index valid index (in any sense, it makes sense for your code). If an invalid value is passed for L or index , this is an error in the code, not a valid error that may occur at run time. If you need help debugging it, the assert macro from assert.h is probably a good idea; this makes it easy to disable validation when you no longer need it.

One of the possible exceptions to the above principle will be the case when the value of L comes from other data structures in your program, but index comes from some external input that is not under your control. Then you can perform the external verification step before calling this function, but if you always need verification, it makes sense to integrate it as you do. However, in this case, you need to report the caller’s failure, and not print the useless and malicious stdout message. Thus, you need to either reserve one possible return value as an error notification, or have an additional argument that allows you to return the result and error status to the caller.

+1
source

Returns a reserved value that is not valid for a success condition. For example, NULL .

It is advisable not to print because:

  • This does not help the cause of the calling error code.
  • You are writing a stream that can be used by higher-level code for something else.
  • The error can be repaired above, so you can just print error error messages.

As others have said, consistency in how you deal with error conditions is also an important factor. But consider the following:

  • If your code is used as a component of another application that does not comply with your printing agreement, then when printing, you do not allow the client code to remain true to its own strategy. Thus, using this strategy, you impose your agreement on all the code associated with it.
  • On the other hand, if you follow a “cleaner” decision to return the reserved value, and the client code wants to follow the print agreement, the client code can easily adapt to what you return, and even print the error, simple wrappers around your functions. Thus, using this strategy, you give users of your code enough space to choose the strategy that suits them best and be faithful to it.
+1
source

It is better to use perror() to display error messages rather than using printf ()

Syntax:

 void perror(const char *s); 

Also, error messages should be sent to the stderr stream than standard output.

+1
source

Best of all, if the code deals with only one thing. This is easier to understand, easier to use and more applicable.

The GetData function, which displays an error message, is not suitable for use in cases where there may be no value. Those. the calling code wants to try to get the value and handle the error using the default value if it does not exist.

Since GetData does not know the context, it cannot report a good error message. As an example, above the level of the call stack, we can report that you forgot to give this user an age, as well as in GetData, where everything he knows cannot get some value.

How about a multithreaded situation? GetData seems like it will be something that can be called from multiple threads. With an accidental IO bit moved in the middle, this will cause a conflict over who has access to the console if all threads should write at the same time.

0
source

All Articles