1

I'm writing a function that takes user input, runs a procedure in our database, and compares the values. Along the way, I need to check that we've received proper input and then that the query has returned an acceptable value.

    private void DoTheThing(int? userInput1, int? userInput2, int valuePassedIn)
    {
        if (userInput1 == null || userInput2 == null)
        {
            Exception ex = new Exception();
            ex.Data.Add("Message", "You screwed up.");
            throw ex;
        }

        var queryResult = 0; //execute a query using the non-null inputs

        if (queryResult == null) //or otherwise doesn't return an acceptable value
        {
            Exception ex = new Exception();
            ex.Data.Add("Message", "some other thing happened");
            throw ex;
        }
        else
        {
            //We're good, so do the thing
        }
    }

A quick note about this: I'm aware of the argument against exceptions as flow control and that I'd be better off checking the user's input before I even get this far. I won't get into all the details, but please accept that I'm kind of stuck writing the function this way.

That having been said, here's my question:

Given that the only differences between the 2 exceptions here is the message and the time at which they are thrown, how can I clean this code up to be both DRY and avoid running unnecessary code after determining that there will be a problem?

I thought about using a goto and placing the error code there, but that really only moves the problem around. If I move the exception code to the bottom and check for a message variable (or something similar), then I'm just running code that doesn't need to be run in the first place.

Community
  • 1
  • 1
levelonehuman
  • 1,325
  • 12
  • 22
  • 1
    Chances are you should be constructing different exceptions. For instance, your first one would logically be an `ArgumentNullException`. – James Thorpe Oct 20 '15 at 14:39
  • 1
    If you intend to throw an exception when userInput1 or userInput2 are null... why are you declaring the parameters as nullable? – BenjaminPaul Oct 20 '15 at 14:41
  • @BenjaminPaul this is because when I initially wrote those parameters as non-nullable, the values defaulted to 0. 0 is an acceptable value here, so I specifically want to capture that they didn't input anything. – levelonehuman Oct 20 '15 at 14:42
  • @JamesThorpe Yes, I agree with you there. I will definitely update the code to be specific; just tossed this together as an example of my problem. – levelonehuman Oct 20 '15 at 14:44
  • Take a look at the heart of [`File.Open`](http://referencesource.microsoft.com/#mscorlib/system/io/filestream.cs,76ef6c04de9d0ed8) - see how many different places it throws exceptions. It's not really a problem at all - I think it just _looks_ like you're repeating yourself too much in your example. – James Thorpe Oct 20 '15 at 14:46
  • Also why are you setting the `Data` with a "Message" instead of setting the exception's message property by using the ctor that takes the message? – juharr Oct 20 '15 at 14:46
  • @juharr this is an example of what I said about being "stuck writing it this way". Our framework looks at `ex.Data` to return the message to the client. – levelonehuman Oct 20 '15 at 14:49
  • @JamesThorpe thank you for the notes, I appreciate the input. – levelonehuman Oct 20 '15 at 14:53
  • @levelonehuman Can you throw something other than `Exception`? If so you could write your own exception class that takes a message but puts it in the `Data` instead. That would get you down to one line. Otherwise I guess a method to create the exception when passed a message, but really that's not gaining you much IMHO. Honestly the real problem sounds like your framework. – juharr Oct 20 '15 at 14:54
  • @juharr I'm *assuming* I can throw a specific type of exception (I don't see why not, but I'm still learning a lot of quirks with this framework), but I agree that writing a new exception class wouldn't give me very much. Really, what felt "wrong" here was writing out the code for throwing an exception at two different points in my method. It's for two different things, but it just seemed odd. Disclaimer, I'm a junior and still learning a lot :) – levelonehuman Oct 20 '15 at 14:58
  • @levelonehuman Where the exception is thrown from is important as that is included in the stack trace and will ultimately show you where the problem is. – juharr Oct 20 '15 at 15:04

4 Answers4

1

You might be better off creating a BadInputException class and a NullQueryResultException class. These do two different things and throwing a specific exception is better than throwing a generic Exception(...). In fact I think FXCop or Visual Studio's Code Analysis will give you a warning about throwing generic Exceptions.

It's not really all that much new code to write.

public class BadInputException : Exception 
{
    public BadInputException()
    {
        this.Data.Add("Message", "You screwed up.")
    }
}

Then instead of this:

Exception ex = new Exception();
ex.Data.Add("Message", "You screwed up.");
throw ex;

Do this:

throw new BadInputException();

Edit: moved the "You screwed up" message from the Message property to the Data collection to match what the OP wants.

user2023861
  • 6,808
  • 6
  • 42
  • 64
  • 2
    `BadInputException` -> `ArgumentNullException`, `ArgumentOutOfRangeException`, `ArgumentException` etc etc. No need to reinvent that one. – James Thorpe Oct 20 '15 at 14:43
  • @JamesThorpe if you want to change the exception message, you'll have to change every instance of `ArgumentNullException` which could be painful. If you create a new class for this, you only need one change. I also find that for logging purposes, it's useful to have more customization to help track down problems. I think it's a question of preference here though. – user2023861 Oct 20 '15 at 18:18
  • Why would you want to change the exception message? An argument is either null or it isn't – James Thorpe Oct 20 '15 at 18:19
  • @JamesThorpe The OP is using messages like `You screwed up.` and `some other thing happened`. I don't think it's out of the realm of possibility that he'll want to change those in the future. – user2023861 Oct 20 '15 at 18:41
1

I suggest not throwing Exception (which means something went wrong, no comments are available), but ArgumentNullException and InvalidOperationException classes. Another amendment is avoding arrow-head antipattern:

private void DoTheThing(int? userInput1, int? userInput2, int valuePassedIn)
{
    // What actually went wrong? An argument "userInput1" is null
    if (null == userInput1)
      throw new ArgumentNullException("userInput1"); 
    else if (null == userInput2)
      throw new ArgumentNullException("userInput2"); // ...or userInput2 is null

    var queryResult = executeSomeQuery(userInput1, userInput2, valuePassedIn);

    // What went wrong? We don't expect that null can be returned;
    // so the operation "executeSomeQuery" failed:
    // we've provided validated (not null) values and got unexpected return.
    // Let it have been known. 
    if (null == queryResult) 
      throw new InvalidOperationException(
        String.Format("Query ({0}, {1}, {2}) returned null when bla-bla-bla expected", 
          userInput1, userInput2, valuePassedIn));   

    // We're good, so do the thing
    // Note that's there's no "arrow-head antipattern": 
    // we're not within any "if" or "else" scope 
}

Edit: Since every *Exception is inherited from Exception you can put some info into Data:

  Exception ex = new ArgumentNullException("userInput1");
  ex.Data.Add("Some key", "Some value");
  throw ex;

but often Message is a far better place to explain what had heppened.

Dmitry Bychenko
  • 149,892
  • 16
  • 136
  • 186
  • Thanks for the response. Is there a way to use `throw new xxException` and have my message specifically input to `.Data`? – levelonehuman Oct 20 '15 at 14:54
  • @levelonehuman: I'd rather put all the information into `Message` (which is expected place for that) like `throw new InvalidOperationException(String.Format("Query bla-bla-bla returned null when ... expected.", ...))`; the origin of the exception (i.e. ` DoTheThing` method) can be be found by stack trace. However, since every exception has `Exception` as a base class you can use `Data` – Dmitry Bychenko Oct 20 '15 at 15:00
  • The only reason I need to use `Data` here is because our framework uses that location to return a message to the client. That's the specific purpose of these exceptions, to let the client know something happened, and not necessarily for debugging purposes. Really, they're not "exceptions" at all (except maybe the query), I'm just forced to masquerade them that way. – levelonehuman Oct 20 '15 at 15:03
  • To your edit: Ah, so the same way I was already adding it, thanks! – levelonehuman Oct 20 '15 at 15:05
  • @levelonehuman: a strange solution; probably, you have to design your own *custom exception class*: *an exception that's not an exception but a media to/within the framework* – Dmitry Bychenko Oct 20 '15 at 15:07
  • Yea, that's basically what exists in our framework already. It's definitely goofy, and it's been an interesting learning experience for me. This framework is currently being rewritten as a result of its many quirks. – levelonehuman Oct 20 '15 at 15:08
0

I would create a method:

private void CheckResult(bool cond, string msg, string info) {
    if (!cond)
        return;

    Exception ex = new Exception();
    ex.Data.Add(msg, info);
    throw ex;
}

and call

CheckResult(userInput1 == null || userInput2 == null, "Message", "You screwed up.");

and

CheckResult(queryResult == null, "Message", "You screwed up.");
Dexion
  • 1,095
  • 8
  • 14
0

I think the QuestionRefactoring Guard Clauses is helpful for you .

There are something about this in Replace Nested Conditional with Guard Clauses.

Hope it's useful.

huoxudong125
  • 1,626
  • 2
  • 23
  • 39