While searching SO for approaches to error handling related to business rule validation, all I encounter are examples of structured exception handling.
MSDN and many oth
The example you give is of UI validating inputs.
Therefore, a good approach is to separate the validation from the action. WinForms has a built in validation system, but in principle, it works along these lines:
ValidationResult v = ValidateName(string newName);
if (v == ValidationResult.NameOk)
SetName(newName);
else
ReportErrorAndAskUserToRetry(...);
In addition, you can apply the validation in the SetName method to ensure that the validity has been checked:
public void SetName(string newName)
{
if (ValidateName(newName) != ValidationResult.NameOk)
throw new InvalidOperationException("name has not been correctly validated");
name = newName;
}
(Note that this may not be the best approach for performance, but in the situation of applying a simple validation check to a UI input, it is unlikely that validating twice will be of any significance. Alternatively, the above check could be done purely as a debug-only assert check to catch any attempt by programmers to call the method without first validating the input. Once you know that all callers are abiding by their contract, there is often no need for a release runtime check at all)
To quote another answer:
Either a member fulfills its contract or it throws an exception. Period.
The thing that this misses out is: What is the contract? It is perfectly reasonable to state in the "contract" that a method returns a status value. e.g. File.Exists() returns a status code, not an exception, because that is its contract.
However, your example is different. In it, you actually do two separate actions: validation and storing. If SetName can either return a status code or set the name, it is trying to do two tasks in one, which means that the caller never knows which behaviour it will exhibit, and has to have special case handling for those cases. However, if you split SetName into separate Validate and Store steps, then the contract for StoreName can be that you pass in valid inputs (as passed by ValidateName), and it throws an exception if this contract is not met. Because each method then does one thing and one thing only, the contract is very clear, and it is obvious when an exception should be thrown.