4

Let's assume we have a method handling operations in a tree hierarchical data structure located in a class handling such a structure.

Let's look closer at one of those methods:

void MoveNode(Node currentNode, Node newParentNode)
{
    /* check if new parent isn't current's child already */
     if (newParentNode.LMargin < currentNode.LMargin && newParentNode.RMargin > currentNode.RMargin)
     {
        //DO WORK
     }
     else throw new ArgumentException("New parent node cannot be current's own child");
}

MSDN states: do not throw Exceptions to control the flow !

My question: is this use of ArgumentException alright in your opinion, or would you use some kind of a return value. If so, how would you supply the error/validation message.

theSpyCry
  • 12,073
  • 28
  • 96
  • 152

4 Answers4

10

Since the exception being thrown indicates a bug here, and it thus won't be ever thrown in a correctly working program and exception is the right choice.

What you should not do is:

try
{
   MoveNode(...)
   //Do something
}
catch(ArgumentException e)
{
  //Do something else
}

In that example you expect the exception being thrown regularly and use it to control the control flow. Catching an ArgumentException in the caller is almost always a bad idea. This kind of exception should only be caught in the top-level handler, if at all.

Personally I don't like you throwing the exception in the else clause. I prefer doing my parameter checking at the beginning of the function and throw the exception immediately afterwards. That prevents nesting the non error code within multiple if blocks.

There are three types of exceptions

  1. Asynchronous exception like StackOverflow, OutOfMemory and ThreadAborted. They can happen anywhere and can't really be handled
  2. Bug exceptions like ArgumentException, log them in the top level handler and fix the bug
  3. Expected exceptions indicating an error that can be handles locally. Typically you use these when errors are uncommon, and you can't know beforehand that an operation will cause an error. IO errors are a typical example of this.
    The cause is typically external. For example an inaccessible file, a network failure or invalid data in a file you're trying to parse.

Eric Lippert talk about these kinds of exception in a blog entry: Vexing exceptions

When to use the third kind of exception, and when to use return values is a judgment call.

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • Totally agree on checking the exception at the beginning. – Liviu Mandras Feb 03 '11 at 09:58
  • OK but if the user interface would allow this assignment, which would also mean that it is part of the flow, and I should avoid catching ArgumentException, would I have to return a value in place of an Exception? – theSpyCry Feb 03 '11 at 10:14
  • 1
    @Pan1c Either throw another type of exception(ArgumentException should only be used for bugs) or use a return value. Which one is better depends on the use of your function. Typically exceptions are used if the cause of the error is external. In your case I'd use a return value. I've added a bit of information on different kinds of exceptions. – CodesInChaos Feb 03 '11 at 10:42
  • It's sometimes hard to decide if an exception is what Eric calls a "Vexing exception" or a "exogenous exception". Experience helps most for that decision, and even then you get it wrong sometimes. It's common to offer two versions of a function, one which returns an error code/boolean indicating success, and one which throws an exception. – CodesInChaos Feb 03 '11 at 10:48
1

I don't think this is the case of "not to use exception for control the flow".

This is argument validation. The method cannot work input parameters aren't valid, so, using an exception is the best way to tell the caller that it's trying to do a bad business.

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • I don't think you try to tell the *caller* about the parameters, but the *programmer* so he fixes the bug. – CodesInChaos Feb 03 '11 at 09:58
  • Yes, you're telling the caller "hey, you're trying to use me in a bad way", and this is told because of the ArgumentException, where you give the parameter name that's wrong. I know you're not going to do almost anything with the "parameter name", but it'll be logged and later you'll fix the bug, because the caller was noticed thanks to the exception. – Matías Fidemraizer Feb 03 '11 at 10:01
  • The (immediate) caller should not catch or care about that exception. It should only be caught be the generic top level exception handler. – CodesInChaos Feb 03 '11 at 10:04
  • @CodeInChors Now I see you thought my suggestion is "the exception will be handled by the caller". I was talking about theory, and the caller is noticed, but it mustn't handle the whole exception. That's all. – Matías Fidemraizer Feb 03 '11 at 10:07
1

If it is a unexpected runtime error, as here, it's an exception, otherwise it should be a return value. Model your decision on int.Parse (throws) / int.TryParse (return value), the first is for circumstances where you know things must be int (parsing a typed structure for example), the other is for validating user input (typing errors are expected from the user).

Exception handling is runtime expensive when thrown, it should avoided in a loop.

Michel
  • 282
  • 1
  • 4
1

Rather than ask whether something should return a value or throw an exception, one should ask what a function is promising to do. If a function promises to move a node, it should throw an exception if it can't. If a function promises to move a node if possible, or indicate via return value that it can't be moved, but only throw an exception if the data structure is fundamentally corrupt in some way beyond that implied by the ability to move the node, it should do that. Sometimes it can be useful to provide functions of both the "do it" and "try it" varieties.

As for what type of exception to throw, I frankly dislike the concept of throwing most built-in exception types from user code, since there's no nice programmatic way to tell whether an ArgumentException was thrown from your routine, or from some routine that was called by your routine, and most exceptions say nothing about the quality of the underlying data structure.

If one is trying to e.g. parse a file from disk and integrate it with an existing data structure and an exception is thrown, it doesn't matter whether the exception was an ArgumentException, or a SubscriptOutOfBoundsException, or a DiskReadErrorException, or whatever. The most important thing is whether the parse attempt was rolled back in such a fashion as to leave the data structure valid; of secondary importance is whether it might be possible to parse the file another way or under other circumstances. The type of exception really only matters to the extent that it can answer those first two questions.

supercat
  • 77,689
  • 9
  • 166
  • 211