[go: nahoru, domu]

Jump to content

Error hiding: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
Jxml01 (talk | contribs)
Added a note of some problems I noticed in this article, including an essay-like viewpoint and use of overly technical language.
Tags: Mobile edit Mobile web edit
Shana (talk | contribs)
Ok so I just rewrote this entire page because of all the issues that it's tagged with. The major change here is that "Error hiding" is not an actual known pattern in programming, it's just a consequence of swallowing errors and exceptions and not reporting this information to users. I've rewritten this to focus on the Error swallowing anti-pattern, which is an actual term that is known in programming, and its consequences.
Line 3: Line 3:
{{Essay|date=April 2019}}
{{Essay|date=April 2019}}
}}
}}
Error swallowing is the practice of catching an error or exception, and then continuing without logging, processing, or reporting the error to other parts of the software. Handling errors in this manner is considered bad practice<ref>{{Cite web|url=http://www-01.ibm.com/support/docview.wss?uid=swg21386753|title=IBM Best Practice: Catching and re-throwing Java Exceptions|date=2009-06-24|website=www-01.ibm.com|language=en-US|access-date=2019-05-02}}</ref> and an [[anti-pattern]] in [[computer programming]]. In languages with [[Exception handling#Exception support in programming languages|exception handling support]], this practice is called exception swallowing.
[[Image:Iceweasel-error-hiding.png|thumb|[[Firefox]]/[[Iceweasel]] hiding a system error message (TCP/IP error [[errno.h|ECONNREFUSED]]) behind a vague description.]]
'''Error hiding''' is an [[anti-pattern]] in [[computer programming]]. The programmer hides error messages by overriding them with [[exception handling]]. As a result of this the root error message is hidden from the user (hence 'error hiding') and so they will not be told what the actual error is. Error hiding is a bane of support engineers' jobs as it often delays the resolution of the problem by hiding information needed to identify what is going wrong.


Errors and exceptions have several purposes:
A common argument for error hiding is the desire to hide complexity from the user. Frequently best practice is to raise an exception to the user to hide a complex error message but to save the full error message to an error log which a support engineer can access to resolve the problem.
* Help software maintainers track down and understand problems that happen when a user is running the software, when combined with a logging system
* Provide useful information to the user of the software, when combined with meaningful error messages, error codes or error types shown in a UI, as console messages, or as data returned from an API (depending on the type of software and type of user)
* Indicate that normal operation cannot continue, so the software can fall back to alternate ways of performing the required task or abort the operation.


When errors are swallowed, these purposes can't be accomplished. Information about the error is lost, which makes it very hard to track down problems. Depending on how the software is implemented, it can cause unintended side effects that cascade into other errors, destabilizing the system. Without information about the root cause of the problem, it's very hard to figure out what is going wrong or how to fix it.
''Example:''
'''try'''
ImportFile(filename);
'''except'''
// an exception with almost no information
raise Exception.Create('import failed');
'''end''';


== Examples ==
This code fragment attempts to open a file and read it into memory. If it fails (for whatever reason) the user only gets a message telling them that the import failed, not why or indeed which file failed to import.


=== Languages with exception handling ===
// better approach
'''try'''
ImportFile(filename);
'''except'''
on E:Exception do
'''begin'''
// build an informative message
E.Message := 'Import of file <'+filename+'> failed.'+#13#10 +
E.Message;
// re-raise the exception
raise;
'''end''';
'''end''';


In this [[C_Sharp_(programming_language)|C#]] example, even though the code inside the ''try'' block throws an exception, it gets caught by the blanket ''catch'' clause. The exception has been swallowed and is considered handled, and the program continues.
In this example the user at least gets a message that tells them which file failed to import; this gives them a start at diagnosing the problem. A more complete solution would include additional information on why the import failed (e.g. "File does not exist", "File appears to be damaged", "Do not have permission to access this file", etc... ) and write the information to a log file, possibly (for very complex or enterprise level applications) also generating extra 'trace' files containing detailed records of the state of the application when the error occurred.


<source lang="csharp">
Sometimes error hiding is a valid activity, for example accessing the contents of a file that does not exist in [[Java (programming language)|Java]] version 1.3 or older would result in an <code>IOException</code> message without any reference to the missing file. In this case it would be sensible to hide the error and raise an exception based on what the application was trying to do at the time, giving what extra information can be obtained.
try {
throw new Exception();
} catch {
// do nothing
}
</source>


In this [[PowerShell]] example, the ''trap'' clause catches the exception being thrown and swallows it by continuing execution. The ''"I should not be here"'' message is shown as if no exception had happened.
Another justification for '''Error Hiding''' is to avoid component crashing in case of failure. Despite the error, the component continues its job. The user (and the testers) never see the crash, even if some anomalies ''can'' be discovered, as the results are not those expected.


<source lang="powershell">
This is accomplished either with a try/catch with an empty catch clause, or by executing code depending on the returning error status:
&{
trap { continue }
throw
write-output "I should not be here"
}
</source>


Exception swallowing can also happen if the exception is handled and rethrown as a different exception, discarding the original exception and all its context.
''Example of try/catch error hiding (C++ code):''


In this C# example, all exceptions are caught regardless of type, and a new generic exception is thrown, keeping only the message of the original exception. The original stacktrace is lost, along with the type of the original exception. If the original exception was a wrapper for other exceptions, that information is lost too.
<source lang="cpp">
#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <cerrno>


<source lang="csharp">
using namespace std;
try {
} catch(Exception ex) {
throw new Exception(ex.Message);
}
</source>


A better way of rethrowing exceptions without losing information is to throw the original exception from the ''catch'' clause:
int main(int argc, char **argv)
{
string result;
try {
ifstream input_file("nonexistent.txt", ios::in);


<source lang="csharp">
if(!input_file) throw strerror(errno);
try {
input_file >> result;
} catch(Exception ex) {
cout << result;
throw;
}
}
catch(...) {
</source>
// Error messages are for wussies
}
cout << "Result is " << result << endl;
return 0;
}


Alternatively, a new exception can be created that wraps the original exception, so any other handlers will have access to both:

<source lang="csharp">
try {
} catch(Exception ex) {
throw new Exception(ex);
}
</source>
</source>


=== Other languages ===
As it is this code is extremely difficult to debug (and is even more, in case of nested "empty try/catch" Error Hiding code), and any anomaly is extremely difficult to trace to its origin, increasing maintenance costs, only to keep up an appearance of robustness.


In [[Go_(programming_language)|Go]], errors are propagated by returning an ''Error'' object along with the normal function return value. It can be ignored, as in this example.
''Example of error returning error hiding (VBScript code):''

<source lang="vb">
<source lang="go">
If doSomething() Then
f, _ := "I should not be here", errors.New("")
If doSomethingElse() Then
fmt.Print(f)
If anotherDoSomething() Then
anotherOtherDoSomething()
End If
End If
End If
</source>
</source>
The consequence is that when some error happens, it is hidden by the code (because it's error prone or simply verbose to add an '''Else''' clause) until someone notices something is amiss.


In the case of [[C_(programming_language)|C]] system calls, errors are indicated by the return value of the call being ''NULL'', and error information is stored in a global ''errno'' variable. This code makes sure the ''file'' is valid before accessing it, but if ''fopen'' failed, the error is swallowed.
A devious consequence is that when some similar code is written, but without the '''If'''/'''End If''' clauses, and is executed after the first code, the second code will fail, but no one will know the failure happened before and was hidden.

<source lang="c">
FILE *file = fopen("", "r");
if (file) {
// do something with the file
}
</source>

== Causes ==

The most common underlying cause of error swallowing is the lack of good logging tools and processes while the developer is building software. When faced with an error that can't be easily handled, if the developer has good logging tools, logging an unexpected error is does not cost the developer any time or effort. Logging the error should be straightforward (one method call), quick (with no impact to application performance), safe (does not raise any errors or exceptions), and ensures that all information is saved, by recording the type of error and any relevant data associated with it, the [[Stack_trace|stacktrace]] of the error (so the developer can identify exactly where the error occurred and what instructions led up to it), and the timestamp of the error.

=== Temporary exception handlers ===


In languages with [[checked exceptions]], all exceptions raised in a method must be listed in a signature of that method. When prototyping and implementing software, code changes often, which means that the type of exceptions that might be raised in a method also change often. Having to adjust the method signature every time something changes slows down development and can be frustrating, so swallowing exceptions as a temporary measure while doing large code changes is appealing. This temporary exception handling code might end up in the released codebase.
Thus one can provoke the appearance of a bug months after the bug was first introduced (but hidden and thus, never discovered).


Even in languages without checked exceptions, adding temporary exception handlers while undergoing large code changes to speed up prototyping can happen, which can lead to error swallowing.
==Manifestations in languages that support checked exceptions==
Error hiding is one of the more common anti-patterns to encounter in languages that support the paradigm of [[checked exceptions]] including [[Java (programming language)|Java]]. This type of exception forces the programmer to handle the exception even if the programmer has no means to effectively do so. The problem becomes more egregious in large or complex systems. For example, a programmer implementing function A may require the service of another component B. Component B may then in turn delegate to component C to fulfill that request. If component C encounters a failure during that service it may indicate this by throwing a checked exception to component B. Component B after recovering from the error but unable to fulfill the request from Function A may indicate this by re-throwing the error from C. Function A, having no recourse to handle an error from component C has three options:


=== Preventing crashes ===
# Re-throw the exception
# Catch the exception, log it and re-throw it
# Catch the exception but do nothing meaningful with it.


In situations where software must not crash for any reason, error swallowing is a practice that a programmer can easily fall into. For example, a [[Plug-in_(computing)|plugin]] that is running inside another application is expected to handle all errors and exceptions in such a way as to not crash the application in which it is embedded. Blanket catching of errors and exceptions is a pattern that is easy to fall into when attempting to prevent crashes at all costs, and when you combine that with poor logging tools, error swallowing can happen.
A novice programmer may choose the third option and hide the error (also referred to as exception swallowing). The consequence of swallowing the exception is that the system may be put into an unstable state but its users (both human and machine) will remain unaware that a critical failure has occurred. Such errors, when finally discovered, are sometimes near impossible to debug, because when it manifests as a noticeable error it is sometimes very separated in the process from where the error actually occurred.


=== Hiding complexity from users ===
Given a large multi-layered application with a high amount of inter-object communication checked exceptions may end up being passed through multiple layers before finally reaching a point where they can be properly handled and reported the end user. In addition to being subject to potential hiding issues at each layer, domain models also become polluted with excessive coupling.
[[File:Generic error message.png|thumb|Error dialog box with a meaningless error message in a desktop application]]
When showing errors to users, it's important to turn cryptic technical errors into messages that explain what happened and what actions the user can take, if any, to fix the problem. While performing this translation of technical errors into meaningful user messages, specific errors are often grouped into more generic errors, and this process can lead to user messages becoming so useless that the user doesn't know what went wrong or how to fix it. As far as the user is concerned, the error got swallowed.


== References ==
For this reason, version 1.4 and later of Java support [[exception chaining]] for all exceptions.
<references />


[[Category:Anti-patterns]]
[[Category:Anti-patterns]]

Revision as of 18:39, 2 May 2019

Error swallowing is the practice of catching an error or exception, and then continuing without logging, processing, or reporting the error to other parts of the software. Handling errors in this manner is considered bad practice[1] and an anti-pattern in computer programming. In languages with exception handling support, this practice is called exception swallowing.

Errors and exceptions have several purposes:

  • Help software maintainers track down and understand problems that happen when a user is running the software, when combined with a logging system
  • Provide useful information to the user of the software, when combined with meaningful error messages, error codes or error types shown in a UI, as console messages, or as data returned from an API (depending on the type of software and type of user)
  • Indicate that normal operation cannot continue, so the software can fall back to alternate ways of performing the required task or abort the operation.

When errors are swallowed, these purposes can't be accomplished. Information about the error is lost, which makes it very hard to track down problems. Depending on how the software is implemented, it can cause unintended side effects that cascade into other errors, destabilizing the system. Without information about the root cause of the problem, it's very hard to figure out what is going wrong or how to fix it.

Examples

Languages with exception handling

In this C# example, even though the code inside the try block throws an exception, it gets caught by the blanket catch clause. The exception has been swallowed and is considered handled, and the program continues.

try {
  throw new Exception();
} catch {
  // do nothing
}

In this PowerShell example, the trap clause catches the exception being thrown and swallows it by continuing execution. The "I should not be here" message is shown as if no exception had happened.

&{ 
  trap { continue }
  throw
  write-output "I should not be here"
}

Exception swallowing can also happen if the exception is handled and rethrown as a different exception, discarding the original exception and all its context.

In this C# example, all exceptions are caught regardless of type, and a new generic exception is thrown, keeping only the message of the original exception. The original stacktrace is lost, along with the type of the original exception. If the original exception was a wrapper for other exceptions, that information is lost too.

try {
} catch(Exception ex) {
    throw new Exception(ex.Message);
}

A better way of rethrowing exceptions without losing information is to throw the original exception from the catch clause:

try {
} catch(Exception ex) {
    throw;
}

Alternatively, a new exception can be created that wraps the original exception, so any other handlers will have access to both:

try {
} catch(Exception ex) {
    throw new Exception(ex);
}

Other languages

In Go, errors are propagated by returning an Error object along with the normal function return value. It can be ignored, as in this example.

f, _ := "I should not be here", errors.New("")
fmt.Print(f)

In the case of C system calls, errors are indicated by the return value of the call being NULL, and error information is stored in a global errno variable. This code makes sure the file is valid before accessing it, but if fopen failed, the error is swallowed.

FILE *file = fopen("", "r");
if (file) {
  // do something with the file
}

Causes

The most common underlying cause of error swallowing is the lack of good logging tools and processes while the developer is building software. When faced with an error that can't be easily handled, if the developer has good logging tools, logging an unexpected error is does not cost the developer any time or effort. Logging the error should be straightforward (one method call), quick (with no impact to application performance), safe (does not raise any errors or exceptions), and ensures that all information is saved, by recording the type of error and any relevant data associated with it, the stacktrace of the error (so the developer can identify exactly where the error occurred and what instructions led up to it), and the timestamp of the error.

Temporary exception handlers

In languages with checked exceptions, all exceptions raised in a method must be listed in a signature of that method. When prototyping and implementing software, code changes often, which means that the type of exceptions that might be raised in a method also change often. Having to adjust the method signature every time something changes slows down development and can be frustrating, so swallowing exceptions as a temporary measure while doing large code changes is appealing. This temporary exception handling code might end up in the released codebase.

Even in languages without checked exceptions, adding temporary exception handlers while undergoing large code changes to speed up prototyping can happen, which can lead to error swallowing.

Preventing crashes

In situations where software must not crash for any reason, error swallowing is a practice that a programmer can easily fall into. For example, a plugin that is running inside another application is expected to handle all errors and exceptions in such a way as to not crash the application in which it is embedded. Blanket catching of errors and exceptions is a pattern that is easy to fall into when attempting to prevent crashes at all costs, and when you combine that with poor logging tools, error swallowing can happen.

Hiding complexity from users

Error dialog box with a meaningless error message in a desktop application

When showing errors to users, it's important to turn cryptic technical errors into messages that explain what happened and what actions the user can take, if any, to fix the problem. While performing this translation of technical errors into meaningful user messages, specific errors are often grouped into more generic errors, and this process can lead to user messages becoming so useless that the user doesn't know what went wrong or how to fix it. As far as the user is concerned, the error got swallowed.

References

  1. ^ "IBM Best Practice: Catching and re-throwing Java Exceptions". www-01.ibm.com. 2009-06-24. Retrieved 2019-05-02.