General design pattern for object error state
Consider a simple class Wallet
that models a wallet. A Wallet
contains a certain amount of Wallet.Cash
and it is possible to take money out / put money in.
public class Wallet
{
/// <summary>
/// Indicates the amount of Cash in the wallet
/// </summary>
public double Cash
{
get;
private set;
}
/// <summary>
/// Takes some money out of the wallet
/// </summary>
public void Spend(double amount)
{
Cash -= amount;
}
/// <summary>
/// Puts some money into the wallet
/// </summary>
public void Fill(double amount)
{
Cash += amount;
}
}
Certain object states are invalid. Those are: Wallet.Cash
is negative, NaN or +/- infinity. We now have the task to take precautions such that transitions into invalid Wallet
states do not happen unnoticed. Which of the following approaches are "industry standard"?
Solution 0: Do not perform any checks. Rely on severe framework testing
A (questionable?) approach is to not change the implementation at all and rely on testing objects that instantiate and manage Wallet
's. After n tests we consider our code working properly.
This approach has the advantage that it optimizes lines of code and performance since no ressources are wasted on integrity checking.
Solution 1: Throw Exception before the object enters an invalid state
Each method could verify that the operation would not lead to an invalid state before executing it. The implementation for the example above would look something like
public class Wallet
{
/// <summary>
/// Indicates the amount of Cash in the wallet
/// </summary>
public double Cash
{
get;
private set;
}
/// <summary>
/// Takes some money out of the wallet
/// </summary>
public void Spend(double amount)
{
if (amount > Cash) throw new Exception("Insufficient cash");
if (0.0d > amount) throw new Exception("Invalid amount");
if (Double.IsInfinity(amount)) throw new Exception("Invalid amount");
if (Double.IsNaN(amount)) throw new Exception("Invalid amount");
Cash -= amount;
}
/// <summary>
/// Puts some money into the wallet
/// </summary>
public void Add(double amount)
{
if (0.0d < amount) throw new Exception("You are intending to spend it");
if (Double.IsInfinity(Cash + amount)) throw new Exception("That's a lot of money in total");
if (Double.IsNaN(amount)) throw new Exception("Invalid amount");
Cash += amount;
}
}
which effectively more than doubles the lines of code, impacting performance and code maintainability. The advantage however is, that the user can catch the Exception
knowing that the Wallet
is still in the state it was before the method was called and hence can consider whether the failed call has invalidated the state of the whole process.
Solution 2: Throw Exception after the object has entered an invalid state
Each method assumes the input paramters to be valid and does its job. At the end, it checks whether anything is now broken. An implementation would look something like
public class Wallet
{
/// <summary>
/// Indicates the amount of Cash in the wallet
/// </summary>
public double Cash
{
get;
private set;
}
/// <summary>
/// Takes some money out of the wallet
/// </summary>
public void Spend(double amount)
{
Cash -= amount;
AssertValidObjectState();
}
/// <summary>
/// Puts some money into the wallet
/// </summary>
public void Add(double amount)
{
Cash += amount;
AssertValidObjectState();
}
private static Exception exInvalidObjectState = new Exception("[Wallet] Invalid Object State");
private void AssertValidObjectState()
{
if(Double.IsInfinity(Cash)) throw exInvalidObjectState;
if(Double.IsNaN(Cash)) throw exInvalidObjectState;
if(0.0d > Cash) throw exInvalidObjectState;
}
}
which adds a tiny snippet at the end of the class and one additional line to each method. While it does not impact code maintainability, it certainly - depending on the complexity of the object - impacts performance and brings the unfortunate side effect, that thrown Exceptions imply the state of the process to be invalid.
Solution 3: Set Object to an error state and throw on next read
Lastly an orthogonal approach that I have seen will not throw the Exception
immediately but rather set the object into an error state and throw it the first time is read from an invalid state. An implementation could look something like
public class Wallet
{
/// <summary>
/// Indicates the amount of Cash in the wallet
/// </summary>
public double Cash
{
get
{
if (null != exInvalidObjectState)
throw exInvalidObjectState;
return cash;
}
private set
{
cash = value;
}
}
private double cash = 0.0d;
/// <summary>
/// Takes some money out of the wallet
/// </summary>
public void Spend(double amount)
{
if (null != exInvalidObjectState)
throw exInvalidObjectState;
Cash -= amount;
AssertValidObjectState();
}
/// <summary>
/// Puts some money into the wallet
/// </summary>
public void Add(double amount)
{
if (null != exInvalidObjectState)
throw exInvalidObjectState;
Cash += amount;
AssertValidObjectState();
}
private static Exception exInvalidObjectState = null;
private void AssertValidObjectState()
{
if (Double.IsInfinity(Cash)) exInvalidObjectState = new Exception();
if (Double.IsNaN(Cash)) exInvalidObjectState = new Exception();
if (0.0d > Cash) exInvalidObjectState = new Exception();
}
}
which implies that it does not really matter whether an object is in an invalid state as long as we don't obtain any data from it. It makes debugging tough and to me seems just wrong to do but ...