Generate and handle user messages inside methods?
What is the best way to handle a function that might occasional fail to evaluate, but even when it does fail does not require a halt of the parent routine, though sometimes might require an explanation to the user?
I have such a function, deep in my code, that returns a number. Codingwise, the simplest approach would be to make the function nullable, and return null when it can't evaluate. That way the calling routine can continue while also knowing the function failed to return a number.
However, depending upon the circumstances, sometimes I want to display the reason for the fail to the user so they know what to fix. Obviously just returning null isn't enough information. Should I be raising messages inside the function itself as it evaluates for anonymous listeners to catch, to be displayed if desired?
(For those rightfully pointing out that logic functions 开发者_开发百科shouldn't be responsible for creating user messages, I didn't mean to imply the function generates the full text message, just somehow transmits the reason for the problem so that the UI could later parse that into a message)The other option I suppose would be throw exceptions inside the function if it can't evaluate, then catch and interpret to user messages if desired. However, as stated the inability to evaluate typically does not signify a halt to the routine, and now I'd have to put a Try...Catch block around the function call everytime I use it.
Exception handling should not be used for flow control. Only throw exceptions for situations that are truly exceptional.
...ahem. Time to get off my high horse.
In all seriousness, I don't know the nature of the problem you're trying to solve. The first question is, is the failure in your algorithm really a failure, or can the inability to evaluate be expressed by a NaN value, or 0? If it truly is a fundamental conceptual failure, is the algorithm able to check the input before proceeding? If so, throw ArgumentException or a class derived thereof - preferably the latter. This means that any other consuming code can handle the general case (for example, in an IoC scenario) and your code can handle the specific case, which it can reasonably be expected to know about. If you do this, I suggest that whatever assembly contains this functionality should also provide some static verification functions that allow the caller to verify that the inbound arguments are valid, before calling something that is likely to throw an exception.
You can pass out
parameter "string for example" to the function, so whenever the function failed, by returning false
or null
print the cause to the user. Just like what Microsoft do with TryParse
.. but here we are getting the reason of the fail as well:
public bool TrySomeFunction(out string errorMessage)
{
try
{
//code that may cause exception
return true;//operation completed successfully
}
catch (Exception exception)
{
errorMessage = exception.Message;
}
return false;
}
Returning NULL is fine if your calling code is expected to know how to deal with NULL values. In this instance I would be logging such times where a NULL (unxpected) value is returned from within the function. Perhaps the function can inform the user via a message box. It really depends on how you have everything set up I guess.
EDIT:
After re-reading the post I seemed to have missed that the code is out of any UI classes. As with the comments, any user display messages should be left to the UI layer to present and not from within any other code. Therefore you need to get a flag of sort sort back to the UI so it can display the message. If this instance is not a function breaker then I would say an exception chain is out of the question. I guess this leaves you with having to determine it in the return value of each function call (like you do in the first instance with the NULL return) - perhaps a custom DataType for return values with a flag for "Warning" or something
I wouldn't throw exceptions, except for in exceptional circumstances. Exceptions are very expensive to throw. Save them for really bad stuff, like the missingrequiredfield jd mentioned. Instead, I'd return an object that has a success flag, the nullable result, and a list of messages that the calling routine can then display to the user (or pass back up to the next layers for displaying to the user)
You can wrap the information you need with a Generic Result class.
public class MyClass
{
public static void Main()
{
var result = new MyClass().TrySomeFunction();
if (result.Succeeded)
{
// use it
MyReturningResultType resultValue = result.GetResultValue();
}
else
{
// use message
string message = result.ResultMessage;
}
}
Result<MyReturningResultType> TrySomeFunction()
{
try
{
MyReturningResultType value = CalculateIt();
return new Result<MyReturningResultType>(value) { Succeeded = true };
}
catch (Exception exception)
{
return new Result<MyReturningResultType>(default(MyReturningResultType))
{
Succeeded = false,
ResultMessage = exception.Message
};
}
}
}
public class Result<T>
{
public Result(T resultValue) { this.ResultValue = resultValue; }
public bool Succeeded { get; set; }
public string ResultMessage { get; set; }
public T GetResultValue()
{
if (ResultValue is T)
{
return (T)this.ResultValue;
}
return default(T);
}
private T ResultValue;
}
to me this sounds really about exception handling.
you can define your own exception types and you can throw one kind of exception when one condition is satisfied ( or not ) and another one in another case.
The caller will decide what to do depending on the exception type thrown, like to notify the user or to set the result of the failing method to null and continue and so on...
Create an exception type for each method of failure. Create user messages based on those exception types. Don't be afraid to add fields to your exception type, so that you can fully recreate a valuable message. This is the biggest failure I usually see in exception programming.
For instance, if you have a required field that the user didn't fill out, throw a MissingRequiredFieldException
which has a MissingFieldName(s)
property. That way, up the stack, you can output a meaningful message to the user about what field(s) were missing.
The best part about this approach, is that it does not rely on the calculation function to generate a user-readable string. That will be done higher up, in an appropriate place. What happens when you have to internationalize? Are you going to refactor your function to switch between languages based on the user? Sounds like a prime case of mixing concerns... What's a calculation function doing handling user-output code?
I agree with @Tom that you shouldn't throw exceptions to just control program flow, as they are expensive. One other approach you might consider is passing a delegate (this is off the top of my head):
public static void Main()
{
Action<string> errorTarget = delegate(string s) { Console.WriteLine(s); };
SomeFunction(errorTarget);
}
private static void SomeFunction(Action<string> errorTarget)
{
...
// Send non-fatal errors to the errorTarget
if (result == null)
{
// Build the error message, then call errorTarget
errorTarget(errorMessage);
}
...
}
精彩评论