This is a simple design decision that seems to have significant passions on each side. I am struggling to really understand which design has the least negative consequences.
I have a method to add a banana:
public Banana AddBanana(string name)
{
// add the banana
var _Banana = new Banana { Name = name };
this.Bananas.Add(_Banana);
return _Banana;
}
But in some cases I cannot add a banana, something like this:
public Banana AddBanana(string name)
{
// test the request
if (this.Bananas.Count > 5)
return null;
if (this.Bananas.Where(x => x.Name == name).Any())
return null;
// add the banana
var _Banana = new Banana { Name = name };
this.Bananas.Add(_Banana);
return _Banana;
}
Now I want to communicate back to the caller WHY they can’t.
WHICH way is the better approach?
Approach 1: communicate with an exception
public Banana AddBanana(string name)
{
// test the request
if (this.Bananas.Count > 5)
throw new Exception("Already 5 Bananas");
if (this.Bananas.Where(x => x.Name == name).Any())
throw new Exception("Banana Already in List");
// add the banana
var _Banana = new Banana { Name = name };
this.Bananas.Add(_Banana);
return _Banana;
}
Approach 2: communicate with a test
public Class CanAddBananaResult
{
public bool Allowed { get; set; }
public string Message { get; set; }
}
public CanAddBananaResult CanAddBanana(string name)
{
// test the request
if (this.Bananas.Count > 5)
return new CanAddBananaResult {
Allowed = false,
Message = "Already 5 Bananas"
};
if (this.Bananas.Where(x => x.Name == name).Any())
return new CanAddBananaResult {
Allowed = false,
Message = "Banana Already in List"
};
return new CanAddBananaResult { Allowed = true };
}
public Banana AddBanana(string name)
{
// test the request
if (!CanAddBanana(name).Allowed)
throw new Exception("Cannot Add Banana");
// add the banana
var _Banana = new Banana { Name = name };
this.Bananas.Add(_Banana);
return _Banana;
}
In Approach 1, the consumer knows the problem based on the exception.Message.
In Approach 2, the consumer can prevent the exception rather than catch it.
Which approach is better overall?
I read this: Design classes so that an exception is never thrown in normal use. For example, a FileStream class exposes another way of determining whether the end of the file has been reached. This avoids the exception that is thrown if you read past the end of the file. http://msdn.microsoft.com/en-us/library/seyhszts(v=vs.71).aspx
But the “exceptional” approach seems to be less code. Does that mean simpler/better?
You should use both.
If it is possible to determine before the method is called whether or not it will be successful, then using a Can() method (or property if the conditions for failure are not specific to the method arguments) is appropriate and useful. There are numerous examples in the framework: TypeConverter.CanConvertFrom/To immediately comes to mind.
However, you cannot guarantee that a caller will use the method you provide to them before calling your method. So you still need to throw appropriate exceptions if the method is called incorrectly (TypeConverter.ConvertFrom will throw under the same conditions that TypeConverter.CanConvertFrom will return false).