2 Golden rules of using Exceptions:
- Fail Fast
- Exceptions should be... well exceptional.
These two rules are contradicting, or are they?
Fail fast dictates that we should throw an exception, when an operation is used on an invalid state.
public class Customer
{
private bool _authenticated;
private Command _command;
private Logger _logger;
public Customer(bool authenticated, Command command, Logger logger)
{
_authenticated = authenticated;
_command = command;
_logger = _logger;
}
}
First of all, we dont let the ctor do anything besides assigning fields. It does not throw exceptions ever! So basically we allow our object to be created in a potentially invalid state.
It is first when an operation is used on an invalid state, that we have to fail fast. And since we dont ever do any operations on object in our ctor, it will never throw an exception.
Methods use fields and arguments in two ways. They pass on the reference to the object or they operate on the object. Only throw exceptions if the method operates on the object.
public class Customer
{
private bool _authenticated;
private Command _command;
private Logger _logger;
public void PassOnLoggerToCommand()
{
//_command is operated on so throw if it is null
//_logger is passed on as a reference, so do not throw.
if(_command == null) throw new InvalidOperationException("command is null. Are you missing a dependency?");
_command.SetLogger(_logger);
}
}
If you throw an Exception from with in a method - not at the top of the method -, that is a good indication that you need to refactor the method into two.
public class Customer
{
private bool _authenticated;
private Command _command;
private Logger _logger;
public void Execute()
{
if(!_authenticated)
{
//Exception throw inside the method (BAD)
if(_logger == null) throw new InvalidOperationException("logger is null. Are you missing a dependency?");
_logger.Info("Attempted login...");
}
else
{
//Exception throw inside the method (BAD)
if(_command == null) throw new InvalidOperationException("command is null. Are you missing a dependency?");
_command.Execute();
}
}
}
Can be refactored to.
public class Customer
{
private bool _authenticated;
private Command _command;
private Logger _logger;
public void Execute()
{
if(!_authenticated)
{
LoggerInfo("Attempted login...");
}
else
{
ExecuteCommand();
}
}
private void ExecuteCommand()
{
//Exception throw at the top of the method (GOOD)
if(_command == null) throw new InvalidOperationException("command is null. Are you missing a dependency?");
_command.Execute();
}
private void LoggerInfo(string message)
{
//Exception throw at the top of the method (GOOD)
if(_logger == null) throw new InvalidOperationException("logger is null. Are you missing a dependency?");
_logger.Info(message);
}
}
Exceptions should be exceptional, and there are performance implications of throwing an exception, so what to do?
Make a query method on the class that checks that a given operation can be made with the current state. So that you never have to catch Exceptions that you create in your own code.
public class Customer
{
private Command _command;
public Customer(Command command)
{
_command = command;
}
public void ExecuteCommand()
{
if(!CanExecuteCommand) throw new InvalidOperationException("command cannot be executed given the current state.?");
_command.Execute();
}
public bool CanExecuteCommand()
{
return _command != null
}
}
Try to make the queries simple questions that return a bool. You might find it that the queries also help when writing automated tests.
A final note, beware of threading issues when using queries.