Is GetLastError() kind of design pattern? Is it good mechanism?

前端 未结 7 1947
庸人自扰
庸人自扰 2021-02-12 15:27

Windows APIs uses GetLastError() mechanism to retrieve information about an error or failure. I am considering the same mechanism to handle errors as I am writing A

相关标签:
7条回答
  • 2021-02-12 15:53

    Overall, it's a bad design. This is not specific to Windows' GetLastError function, Unix systems have the same concept with a global errno variable. It's a because it's an output of the function which is implicit. This has a few nasty consequences:

    1. Two functions being executed at the same time (in different threads) may overwrite the global error code. So you may need to have a per-thread error code. As pointed out by various comments to this answer, this is exactly what GetLastError and errno do - and if you consider using a global error code for your API then you'll need to do the same in case your API should be usable from multiple threads.

    2. Two nested function calls may throw away error codes if the outer function overwrites an error code set by the inner.

    3. It's very easy to ignore the error code. In fact, it's harder to actually remember that it's there because not every function uses it.

    4. It's easy to forget setting it when you implement a function yourself. There may be many different code paths, and if you don't pay attention one of them may allow the control flow to escape without setting the global error code correctly.

    Usually, error conditions are exceptional. They don't happen very often, but they can. A configuration file you need may not be readable - but most of the time it is. For such exceptional errors, you should consider using C++ exceptions. Any C++ book worth it's salt will give a list of reasons why exceptions in any language (not just C++) are good, but there's one important thing to consider before getting all excited:

    Exceptions unroll the stack.

    This means that when you have a function which yields an exception, it gets propagated to all the callers (until it's caught by someone, possible the C runtime system). This in turn has a few consequences:

    1. All caller code needs to be aware of the presence of exceptions, so all code which acquires resources must be able to release them even in the face of exceptions (in C++, the 'RAII' technique is usually used to tackle them).

    2. Event loop systems usually don't allow exceptions to escape event handlers. There's no good concept of dealing with them in this case.

    3. Programs dealing with callbacks (plain function pointers for instance, or even the 'signal & slot' system used by the Qt library) usually don't expect that a called function (a slot) can yield an exception, so they don't bother trying to catch it.

    The bottom line is: use exceptions if you know what they are doing. Since you seem to be rather new to the topic, rather stick to return codes of functions for now but keep in mind that this is not a good technique in general. Don't go for a global error variable/function in either case.

    0 讨论(0)
  • 2021-02-12 15:53

    If your API is in a DLL and you wish to support clients that use a different compiler then you then you cannot use exceptions. There is no binary interface standard for exceptions.

    So you pretty much have to use error codes. But don't model the system using GetLastError as your exemplar. If you want a good example of how to return error codes look at COM. Every function returns an HRESULT. This allows callers to write concise code that can convert COM error codes into native exceptions. Like this:

    Check(pIntf->DoSomething());
    

    where Check() is a function, written by you, that receives an HRESULT as its single parameter and raises an exception if the HRESULT indicates failure. It is the fact that the return value of the function indicates status that allows this more concise coding. Imagine the alternative of returning the status via a parameter:

    pIntf->DoSomething(&status);
    Check(status);
    

    Or, even worse, the way it is done in Win32:

    if (!pIntf->DoSomething())
        Check(GetLastError());
    

    On the other hand, if you are prepared to dictate that all clients use the same compiler as you, or you deliver the library as source, then use exceptions.

    0 讨论(0)
  • 2021-02-12 15:56

    The GetLastError pattern is by far the most prone to error and the least preferred.

    Returning a status code enum is a better choice by far.

    Another option which you did not mention, but is quite popular, would be to throw exceptions for the failure cases. This requires very careful coding if you want to do it right (and not leak resources or leave objects in half-set-up states) but leads to very elegant-looking code, where all the core logic is in one place and the error handling is neatly separated out.

    0 讨论(0)
  • 2021-02-12 15:56

    I have to say, I think the global error handler style (with proper thread-local storage) is the most realistically applicable when exception-handling cannot be used. This is not an optimal solution for sure, but I think if you are living in my world (a world of lazy developers who don't check for error status as often as they should), it's the most practical.

    Rationale: developers just tend to not check error return values as often as they should. How many examples can we point to in real world projects where a function returned some error status only for the caller to ignore them? Or how many times have we seen a function that wasn't even correctly returning error status even though it was, say, allocating memory (something which can fail)? I've seen too many examples like these, and going back and fixing them can sometimes even require massive design or refactoring changes through the codebase.

    The global error handler is a lot more forgiving in this respect:

    • If a function failed to return a boolean or some ErrorStatus type to indicate failure, we don't have to modify its signature or return type to indicate failure and change the client code all over the application. We can just modify its implementation to set a global error status. Granted, we still have to add the checks on the client side, but if we miss an error immediately at a call site, there's still opportunity to catch it later.

    • If a client fails to check the error status, we can still catch the error later. Granted, the error may be overwritten by subsequent errors, but we still have an opportunity to see that an error occurred at some point whereas calling code that simply ignored error return values at the call site would never allow the error to be noticed later.

    While being a sub-optimal solution, if exception-handling can't be used and we're working with a team of code monkeys who have a terrible habit of ignoring error return values, this is the most practical solution as far as I see.

    Of course, exception-handling with proper exception-safety (RAII) is by far the superior method here, but sometimes exception-handling cannot be used (ex: we should not be throwing out of module boundaries). While a global error handler like the Win API's GetLastError or OpenGL's glGetError sounds like an inferior solution from a strict engineering standpoint, it's a lot more forgiving to retrofit into a system than to start making everything return some error code and start forcing everything calling those functions to check for them.

    If this pattern is applied, however, one must take careful note to ensure it can work properly with multiple threads, and without significant performance penalties. I actually had to design my own thread-local storage system to do this, but our system predominantly uses exception-handling and only this global error handler to translate errors across module boundaries into exceptions.

    All in all, exception-handling is the way to go, but if this is not possible for some reason, I have to disagree with the majority of the answers here and suggest something like GetLastError for larger, less disciplined teams (I'd say return errors through the call stack for smaller, more disciplined ones) on the basis that if a returned error status is ignored, this allows us to at least notice an error later, and it allows us to retrofit error-handling into a function that wasn't properly designed to return errors by simply modifying its implementation without modifying the interface.

    0 讨论(0)
  • 2021-02-12 15:57

    Exception handling in unmanaged code is not recommended. Dealing with memory leaks without exceptions is a big issue, with exception it becomes nightmare.

    Thread local variable for error code is not so bad idea, but as some of the other people said it is a bit error prone.

    I personally prefer every method to return an error code. This creates inconvenience for functional methods because instead of:

    int a = foo();
    

    you will need to write:

    int a;
    HANDLE_ERROR(foo(a));
    

    Here HANDLE_ERROR could be a macro that checks the code returned from foo and if it is an error propagates it up (returning it).

    If you prepare a good set of macros to handle different situations writhing code with good error handling without exception handling could became possible.

    Now when your project start growing you will notice that a call stack information for the error is very important. You could extend your macros to store the call stack info in a thread local storage variable. That is very useful.

    Then you will notice that even the call stack is not enough. In many cases an error code for "File not found" at line that say fopen(path, ...); does not give you enough information to find out what is the problem. Which is the file that is not found. At this point you could extend your macros to be able to store massages as well. And then you could provide the actual path of file that was not found.

    The question is why bother all of this you could do with exceptions. Several reasons:

    1. Again, Exception handling in unmanaged code is hard to do right
    2. The macro based code (if done write) happens to be smaller and faster than the code needed for exception handling
    3. It is way more flexible. You could enable disable features.

    In the project that I am working at the moment I implemented such error handling. It took me 2 days to put in a level to be ready to start using it. And for about a year now I probably spend about 2 weeks total time of maintaining and adding features to it.

    0 讨论(0)
  • 2021-02-12 15:57

    You should also consider a object/structure based error code variable. Like the stdio C library is doing it for FILE streams.

    On some of my io objects for example, i just skip all further operations when the error state is set so that the user is fine just when checking the error once after a sequence of operations.

    This pattern allows you to finetune the error handling scheme much better.

    One of the bad designs of C/C++ comes to full light here when comparing it for example with googles GO language. The return of just one value from a function. GO does not use exceptions instead it always returns two values, the result and the error code.

    There is a minor group of people who think that exceptions are most of the time bad and misused because errors are not exceptions but something you have to expect. And it hasn't proved that software becames more reliable and easier. Especially in C++ where the only way to program nowadays is RIIA techniques.

    0 讨论(0)
提交回复
热议问题