Case #500: The secret exception

Developer Crimes
4 min readJan 24, 2021

The user vs the developer

Three months ago, the user purchased a C# method from a developer, and after using the it a few times in a critical system, claims that it was falsely advised since it does not work as promised. The user has demanded compensation for the damages but the developer has denied these allegations and today, the matter will be settled in court.

Plaintiff: User

Defendant: Developer

Proceedings

Judge

Members of the jury, your duty today will be to determine whether the defendant is guilty or not based only on facts and evidence provide in this case.

Evidence (C#)

public static int Divide(int dividend, int divisor)
{
if(divisor == 0)
{
throw new ArgumentException("Cannot divide by zero");
}

return dividend / divisor;
}

Plaintiff

Your honor, the defendant assured me that the Divide method will return an integer when I pass it two integers. When I passed zero, as the divisor, the method threw an exception. Since zero is a valid integer, and the method threw an exception instead of returning an integer, the defendant lied to me and cost me millions of dollars in damages.

Defendant

This is ridiculous! The plaintiff knows that you cannot divide an integer by zero, your honor. This is common knowledge and my code throws an exception to let the user know that, since it considers zero to be invalid input. I did not explicitly share this with the plaintiff, but I assure you, my intensions are pure.

Verdict

We, the jury, in the case of the user vs the developer, find the defendant guilty of the charge of lying to the user.

Judge

Thank you, Jury, for your service today. The court is adjourned.

Lesson Learned

Let’s take a closer look at the evidence, and find out how this developer could have saved themselves some time and money.

Functional programming is based on the simple premise that your methods should not have side effects. In other words, your method name should be behave in a predictable way under ALL conditions. The side effect we are referring to, in the code above, is the ArgumentException. We are essentially saying, our functions returns an integer, BUT only under certain conditions. These conditions are often documented but rarely enforced and are subject to change. The lack of enforcement in code, is what permits the user to make mistakes. We must lean on the compiler to enforce rules through static type checking whenever possible. In other words, embrace the principle of least astonishment.

Here are two approaches we can consider to keep our method honest.

1. Keep your input gates honest

public static int Divide(int dividend, NotZeroInteger divisor)
{
return dividend / divisor.Value;
}

NotZeroInteger example

public readonly struct NotZeroInteger
{
public int Value { get; }

public NotZeroInteger(int value)
{
if(value == 0) throw new ArgumentException("Value cannot be 0");
Value = value;
}
}

We decided to define a NotZeroInteger struct, a value type, that we use to guarantee that we will never see a zero inside the method. Since we do not have to interrogate the input, we can guarantee that this method will always return an integer under all conditions. The key is to enforce accepting objects that are in a valid state.

Note: An exception will be thrown when attempting to construct a NotZeroInteger by passing zero. In c#, we cannot enforce this statically.

2. Keep your output gates honest

public static Result<int> Divide(int dividend, int divisor)
{
if (divisor == 0)
{
return Result.Fail<int>("Cannot divide by zero");
}

return Result.Ok(dividend / divisor);
}

We changed the return type to a Result object which can be a Success or Failure. The user, now, has the ability to make a decision on what to do in both conditions and is not forced to handle an exception. The user can choose to ignore failure cases without worrying about an exception. The same can be accomplished using nullable types i.e. int? in this case.

var result = Divide(int.MinValue, 2);

if (result.Success)
{
Console.WriteLine(result.Value);
}
else
{
Console.WriteLine(result.Error);
}

Which approach do I recommend? The answer is, it depends :) It depends on what the user wants. It depends on what the team prefers. I prefer only allowing immutable types wherever possible as well as giving the user the power of choice.

Which of these approaches do you prefer, and why? Are there any other approaches you have used?

Conclusion

Honesty is always the best policy. Obviously, this was a trivial example but I am sure there are many cases you might have encountered, where exceptions are thrown without much thought and that can lead to readability and maintainability issues. Developers should take time to evaluate whether an exception is the right approach in any given scenario. Exceptions are ideal for exceptional circumstances that can't be recovered from, and not for routine error handling. The case described here is a classic case of routine error handling.

Let me know what you think. What lies have you seen or told? See you at the next court case.

References

  1. Result<T> implementation
  2. Functional programming design patterns by Scott Wlaschin

--

--