다음을 통해 공유


Design Guidelines Update: Exception Throwing

Exception Throwing

Exception throwing guidelines described in this section require a good definition of the meaning of execution failure. Execution failure occurs whenever a member cannot do what it was designed to do (what the member name implies). For example, if OpenFile method cannot return an opened file handle to the caller, it would be considered an execution failure.

Most developers have become comfortable with using exceptions for hard error cases such as division by zero or null references. In the Framework, exceptions are used for both hard errors and logical errors. At first, it can be difficult to embrace exception handling as the means of reporting all functional failures. However, it is important to design all public methods of a framework to report method-failures by throwing an exception.

There are a variety of excuses for not using exceptions, but most boil down to the two perceptions that exception handling syntax is undesirable, so returning an error code is somehow preferable, or that a thrown exception does not perform as well as returning an error code. The performance concerns are addressed in the performance section below. The concern over syntax is largely a matter of familiarity and should not be a consideration. As an API designer we should not make assumptions about the familiarity of the application developers consuming our code.

ý Do not return error codes. Exceptions are the primary means of reporting errors in frameworks.

þ Do report execution failures by throwing exceptions. If a member cannot successfully do what is designed to do, it should be considered an execution failure and an exception should be thrown.

þ Consider terminating the process by calling System.Environment.FailFast (.NET Framework 2.0 feature) instead of throwing an exception, if your code encounters a situation where it is unsafe for further execution.

ý Do not use exceptions for normal flow of control. Except for system failures, there should generally be a way to write code that avoids exceptions being thrown. For example, you can provide a way to check preconditions before calling a member to allow users to write code that does not throw exceptions.

ICollection<int> collection = …

if(!collection.IsReadOnly){

    collection.Add(additionalNumber);

}

The member used to check preconditions of another member is often referred to as a tester and the member that actually does the work is called a doer. See performance section below for more information on the Tester-Doer Pattern.

There are cases when the Tester-Doer pattern may have an unacceptable performance overhead. In such cases the so called TryParse Pattern (see section below) should be used.

þ Consider performance implications of throwing exceptions. See section below for details.

þ Do document all exceptions thrown by publicly callable members because of a violation of the member contract (rather than a system failure) and treat them as part of your contract. Exceptions that are a part of the contract should not change from one version to the next.

ý Do not have public members that can either throw or not based on some option.

Type GetType(string name, bool throwOnError)

ý Do not have public members that return exceptions as the return value or an out parameter.

þ Do set all the relevant properties of the exception you throw.

þ Consider using exception builder methods. It is common to throw the same exception from different places. To avoid code bloat, use helper methods that create exceptions and initialize their properties. For example:

class File{

   string fileName;

 

  public byte[] Read(int bytes){

      if (!ReadFile(handle, bytes))

            throw NewFileIOException(...);

   }

 

   FileException NewFileException(...){

      string description = // build localized string

      return new FileException(description);

   }

}

ý Do not throw exceptions from exception filter blocks. When an exception filter raises an exception, the exception is caught by the CLR, and the filter returns false. This behavior is indistinguishable from the filter executing and returning false explicitly and is therefore very difficult to debug.

ý Avoid explicitly throwing exceptions from finally blocks. Implicitly thrown exceptions resulting from calling methods that throw are acceptable.

1.1.1 Choosing the Right Type of Exception to Throw

þ Consider throwing existing exceptions residing in the System namespaces instead of creating custom exception types.

þ Do create and throw custom exceptions if you have an error condition that can be programmatically handled in a different way than any other existing exception. Otherwise, throw one of the existing exceptions.

ý Do not create and throw new exceptions just to have ‘your team's’ exception.

þ Do throw the most specific (the most derived) exception that makes sense. For example, throw ArgumentNullException and not its base type ArgumentException if a null argument is passed.

1.2 Exceptions and Performance

One common concern related to exceptions is that if exceptions are used for code that routinely fails, the performance of the implementation will be unacceptable. This is a very valid concern. When a member throws an exception, its performance can be orders of magnitude slower. However, it is possible to achieve good performance while strictly adhering to the exception guidelines that disallow using error codes. Two patterns described in this section suggest ways to do this.

ý Do not use error codes because of concerns that exceptions might affect performance negatively.

Tester-Doer Pattern

Sometimes performance of an exception throwing member can be improved by breaking the member into two. Let’s look at the indexed property of the Dictionary class.

Dictionary<string,int> table = new Dictionary<string,int>();

int value = table[“key”];

The indexer throws if the key does not exist in the Dictionary. This can be a performance problem in scenarios where the lookup is expected to fail often. One of the ways to mitigate the problem is to test whether the key is in the dictionary before accessing the value.

Dictionary<string,int> table = new Dictionary<string,int>();

if(table.Contains(“key”)){

    int value = table[“key”];

}

The member used to test a condition, in our example the method Contains, is referred to as the ‘tester’. The member used to perform a potentially throwing operation, in our example the indexer, is referred to as the ‘Doer’.

þ Consider the Tester-Doer pattern for members which may throw exceptions in common scenarios to avoid performance problems related to exceptions.

TryParse Pattern

For extremely performance demanding APIs, an even faster pattern than the Tester-Doer described in the previous section should be used. The pattern is to adjust the member name to make a well-defined test case a part of the member semantics. For example, DateTime defines a Parse method that throws if parsing of a string fails. It also defines a corresponding TryParse method which attempts to parse, but returns false if parsing is unsuccessful and returns the result of a successful parsing using an out parameter.

public struct DateTime {

    public static DateTime Parse(string dateTime){

        …

    }

    public static bool TryParse(string dateTime, out DateTime result){

        …

    }

}

When using this pattern, it is important to define the ‘try’ functionality in strict terms. If the member fails for any reason other than the well defined try, then the member must still throw.

þ Consider the TryParse pattern for members which may throw exceptions in common scenarios to avoid performance problems related to exceptions.

þ Do provide an exception-throwing member for each member using the TryParse pattern.

Comments

  • Anonymous
    March 16, 2005
    &quot;Do not create and throw new exceptions just to have ‘your team's’ exception.&quot;<br><br>I was planning to do exactly that so we can identify exceptions thrown by our own code.<br><br>Could you explain why this is a bad idea ?<br><br>thanks.

  • Anonymous
    March 16, 2005
    Python uses what is called the "samurai principle" for determining when to throw exceptions:

    Return a meaningfull and useable result or else throw an exception.

    I find that the standard prescription "exceptions are for exceptional circumstances" doesnt give much guidance, and welcome these new guidelines.

  • Anonymous
    March 16, 2005
    "Do not return error codes. Exceptions are the primary means of reporting errors in frameworks."

    Blech. One more reason for me to stay away from .net.

  • Anonymous
    March 16, 2005
    So what do you make of Thread.Abort() that (on Whidbey, if the thread is suspended) throws an exception and schedules an abort?

  • Anonymous
    March 17, 2005
    Question on AgumentNullException.<br><br>What exception to throw if my method does not allow &quot;&quot; (empty string) and such argument was passed in?<br><br>In some place of CLR I have seen ArgumentNullException, but String.Empty is definitely not null. What is the guideline here?<br><br>Please claim this option to official document, that all developers have to deal same way.

  • Anonymous
    March 17, 2005
    &quot;Do not create and throw new exceptions just to have ‘your team's’ exception.&quot;<br><br>Could you explain why ?<br><br>We were planning to do this so we can identify the exceptions thrown by our code.<br>

  • Anonymous
    March 17, 2005
    The comment has been removed

  • Anonymous
    March 17, 2005
    Jeroen, you're correct about the behavior.<br><br>I wouldn't recommend following the pattern that Thread.Abort() uses. (Note: I wouldn't recommend suspending and asynchronously aborting threads either, but that's a wildly separate topic. :))<br><br>The behavior is strange. It succeeds at provoking an abort, but does throw an exception.<br><br>I wish we could change it, but it's been that way since 1.0.

  • Anonymous
    March 17, 2005
    What does FailFast do? I can't find any documentation on it.

  • Anonymous
    March 18, 2005
    This guideline is mostly excellent, but confusing in one respect.

    This performance rule:

    "Do not use error codes because of concerns that exceptions might affect performance negatively."

    is violated by the TryParse pattern. The TryParse pattern is worded nicely, but still clearly uses an error code to indicate success or failure.

    This contradiction, and the confusion that comes with it, disappear if you qualify the first rule with "except in extremely performance demanding APIs," and then suggest to "Do use" the TryParse pattern for these situations.

  • Anonymous
    March 18, 2005
    System.Environment.FailFast offers the user a chance to send an error report, then terminates the process. (It's the nicer managed equivalent to calling ExitProcess when you decide you really can't go on.) If you're writing a class library you probably don't want to use FailFast--at least not if you like having customers :-D. If you're writing an application, though, it often makes sense if you really want the process to die. Exceptions can be caught; FailFast just kills the process right away.

  • Anonymous
    March 18, 2005
    Here's how I think about the performance rule and Parse vs. TryParse:<br><br>Parse says it’s going to parse something. “Failure” means it couldn’t parse it. So exceptions from Parse could include exceptions because what you passed to it wasn’t parseable. The return value is the result of the operation—the parsed value.<br><br>TryParse says it’s going to try to parse something. “Failure” means that it wasn’t even able to try to parse the thing. Thus the only kinds of exceptions you’re going to get out of TryParse are things like OutOfMemory from JITting or allocating buffers before it even gets a chance to look at the string to be parsed. (You wouldn’t expect to get an exception because the value wasn’t parseable…the point is the method said it was going to try, and it did try. It did what it said it would, so no exception needed.) TryParse really has two results—was the thing parseable, and if so the parsed value.<br>

  • Anonymous
    March 20, 2005
    Hello,

    FYI: your blog renders completely wrong in Firefox.

  • Anonymous
    March 29, 2005
    "FYI: your blog renders completely wrong in Firefox."

    Um... In IE, too!?!? (IE6 on Wn2003)

    OTOH, like the contents and the reasoning.

  • Anonymous
    March 30, 2005
    It isn't clear whether you prohibit exceptions from /containing/ error codes (as a property). This ought to be allowed, because in some circumstances your only other options are (a) having a huge set of different exception types or (b) parsing the exception message to figure out what exactly happened.<br>An example of this problem is XmlException, which can be thrown for 1000 different reasons and needs an error code property to distinguish it.

  • Anonymous
    May 27, 2005
    Recently somebody asked me to clarify one of the exception guidelines. They were asking whether it's...

  • Anonymous
    June 06, 2006
    In a recent post I mentioned the .NET framework design guidelines and that reminded me of a post by Krzyztof...

  • Anonymous
    July 05, 2006
    My last post about the ApplicationException resulted in some questions along the lines of “so, if not...

  • Anonymous
    July 29, 2006
    The use of the word Throw has mulitple meanings in .net.  As a result the article is somewhat ambiguous.  In object oriented design, the standard of an object is that it is responsible for it's own function.  Therefore, it is my understanding that objects and or methods within, should not Throw anything.  Instead they should handle the exception and move on.  Does your term throw mean "Handle the exception and inform the user?"  

  • Anonymous
    July 29, 2006
    I agree that the term “throwing” is a bit ambiguous. It means either executing the throw statement or allowing an exception propagate out of a publicly callable API.

    I am not sure why you interpret throwing as handling exceptions and showing them to users. Could you point me to any particular statements that might imply so?

    As to objects handling exceptions and moving on, I think it’s only true for some kinds of exceptions. There are others that simply cannot be handled because the object does not have enough context to know how to fix the state of the system in the exception handler so it could continue safely. Those can sometimes be handled higher up on the stack, but in some cases they cannot be handled at all, and the best approach is to record as much information as possible and shut down the process/app domain.

    JP, please feel free to email me directly (kcwalina_at_microsoft.dot_com) if you'd like to talk about this more.

  • Anonymous
    September 11, 2006
    I came across this article on Exception Throwing by Krysztof Cwalina, and I didn't think its all very good advice... (See http://www.alski.net/software/Throwing+Exceptions.aspx)

  • Anonymous
    September 14, 2006
    AlSki, I posted a reply comment on your blog. I hope it clarifies some things. Please don't hesitate to email me at kcwalina_at_microsoft.com. I would be happy to discuss more detail.

  • Anonymous
    January 30, 2007
    Can you provide some guidance for how to structure a hierarchy of exception classes?  I cannot find any information addressing this design issue.  Should exceptions be grouped by the type of data that the error is associated with, or by the type of error.  I think they should be organized by the type of error, but it is very difficult to group errors this way.  Thank you for any input you may have.

  • Anonymous
    February 21, 2007
    How would you recommend handling multiple 'error' types that are passed back from hardware devices? For example we are a driving a motor to cause movement, and the command 'move()' can fail for many reasons such as 'HitEndStop', "Stall", "PowerLoss". Currently we would do something like: CError MoveTheDevice() {    CError error = Move();    if (error.IsOk)    {    //... continue processing    }    return error;   // to pass status higher up } Should we create lots of exception types and map the device errors to exception types? or should we throw an exception, having recorded the 'error' code somewhere - maybe as a 'parameter'  within the exception.

  • Anonymous
    April 25, 2007
    Krzysztof , Thank you for the great article.  Your ideas are clear and well laid out, and have helped tremendously. NigelAston, One way I deal with this scenario is using a "base" exception. public abstract class HardwareFailureException : System.ApplicationException public class HitEndStopException : HardwareFileException public class PowerLossException : HardwareFileException Then I believe you can just catch the HardwareFileException exception in your code. Give it a try!

  • Anonymous
    July 19, 2007
    Performance is a criteria to take into consideration, but one criteria that is not included in this article is code flow, throwing exceptions sometimes makes you code flow nicer than trying to exit your method earlier and trying to clean up, when you finally statement could handle that, returning error codes from your method when it happens, etc.... I assume that this guidelines apply to exceptions that could occur in .net system classes, but what about classes that I create? If have not seen any issues with performance in throwing exceptions in my production application...It has made the flow of my program much cleaner and easier to understand.

  • Anonymous
    December 09, 2007
    Buff... cuanto tiempo desde mi último post, entre estudiar y la medio-gripe que arrastro desde hace unas

  • Anonymous
    December 09, 2007
    Buff... cuanto tiempo desde mi último post, entre estudiar y la medio-gripe que arrastro desde hace unas

  • Anonymous
    January 01, 2008
    PingBack from http://imehta.com/blog-ketan/?p=15

  • Anonymous
    January 09, 2008
    &#352;odien &#160; Vakar Andrejs jau aizskāra tēmu, par kuru es jau pasen gribu uzrakstīt, bet kaut kā

  • Anonymous
    January 09, 2008
    Gud one. how we can throw exceptions from Appdomain "A" to appDomain "B". Scenario: In AppDomain "B" create a thread and from worker thread call a method in AppDomain "A" , which throws an exception. How we can catch this exception in AppDomain "B"

  • Anonymous
    March 06, 2008
    PingBack from http://blog.sanny.maniacs.nu/2008/03/07/net-exceptions-performance-and-guidelines/

  • Anonymous
    March 07, 2008
    Exception handling plays an important part of application management and user experience. If implemented

  • Anonymous
    March 09, 2008
    Hi Krzysztof, Iv'e bought your book and is one of my favourites. I wanna ask if you can spend some word about the bad design to rise normal events (named "whateverError" or "whateverException") instead of throwing exceptions. thanks.

  • Anonymous
    March 11, 2008
    In rare cases it's better to communicate errors through events. This is true mainly for asynchronous APIs where the original caller thread is long doing some other things and we can not throw to that thread anymore.

  • Anonymous
    March 11, 2008
    Thanks for your reply. So except for multithreading environment, throwing exception is always the best way. I have many colleagues that sadly mix up event and exception so "you can manage what you prefer!" ... :| Could you please write a little explanation about this in your new book?, so if "the .net guidelines bible said so", they can't complain... :) regards!

  • Anonymous
    March 17, 2008
    I will try to clarify that in the book (2nd edition), but it already says that exceptions should be the default and other means of communicating errors should be very rare.

  • Anonymous
    March 25, 2008
    PingBack from http://blog.todobom.dk/post/Liste-over-exceptions-i-NET.aspx

  • Anonymous
    March 27, 2008
    This post was originally published here . I recently posted on the out and ref keywords in C#, and mentioned

  • Anonymous
    May 25, 2008
    A person asked above what should do in case the method called gets an empty string (String.Empty) and such a value is not acceptable? Throwing an ArgumentNullException is not the best idea and ArgumentException might be a little too ambigous. I always thought of ArgumentEmptyException as an exception class derived from ArgumentException. What can you tell us about this issue?

  • Anonymous
    July 31, 2008
    PingBack from http://blog.lukesmith.net/index.php/2008/07/31/sometimes-you-have-to-catch-an-exception/

  • Anonymous
    January 18, 2009
    mihailik: AFAIK, there is no built in Exception class to check for empty arguments. What I can suggest is this: In your method, do this: if String.IsNullOrEmpty(param) {   throw new ArgumentException("The param cannot be null or an empty string."); } Can somebody comment?

  • Anonymous
    March 21, 2010
    "TryDo" sounds like more appropriate name for what you call "TryParse" pattern. Or am I wrong?

  • Anonymous
    June 16, 2011
    Ivan, it's possibly a better name, but TryParse just stuck for historical reasons.