Here's a function. According to its docs, the returned string holds the text representation of the chant heard in response.

string Chant(string word, int count, bool? dryRun);

I find it useful to think about the domains and ranges of functions in C#, though I admit this kind of analysis has limited use in a language which cares little about side effects. For anyone not in the know, a function domain defines the set of inputs for which the function is defined. What constitutes 'defined' in C# is anyone's guess, but I'm going to assert that Chant is defined for all combinations of all possible values of its inputs.

The variable dryRun is either true, false, or null. The possible values for count are anywhere between -2,147,483,648 and 2,147,483,647 inclusive.

The possible values a string can hold are less obvious. Limitations in the language itself prevent programs declaring strings longer than about a billion characters in length, but we needn't bother with such practicalities. For our purposes, a string is simply a list of chars, where said list has length 0 or more, and each value is between U+0000 and U+FFFF inclusive.

In other words, the domain of Chant is infinite (theoretically), but I can express it clearly: Chant is defined for any tuple containing any list of chars (0 or more), any number between -2,147,483,648 and 2,147,483,647 inclusive, and one of true, false, and null. A possible input: ("verisimilitude", 12, false).

What about its range? That's harder to answer. It could be null (but I'm hoping that whoever wrote this function would've made it return string? if that were possible. It might return, as we stated above, a list of chars of some length, maybe 0, maybe enough to run out of memory. But it might "return" an exception. The type of the exception is anyone's guess but I can at least assert it'll be a subclass of Exception.

That's a pretty big range — obviously infinite, but now open to an infinite number of types as well as infinite length arrays of chars. Of these extra types, some might be exceptions that we definitely don't want to catch, like OutOfMemoryException, StackOverflowException, and (for anyone familiar) MoqException (call them 'panicky' exceptions) — but others are exceptions we're meant to understand in the context of the program because we might be able to recover from them. We can't tell which is which except by trust, luck, or very good documentation. Absolute madness.


Sensible developers would give up now and try a different language, but we can attempt to make this easier for people wishing to chant anyway. Clearly it isn't useful to include panicky exceptions in the range of the function because (not to put too fine a point on it) they're exceptions. However, there are other exceptions that should be understood in the flow of the program, and they should be semantically separated from panicky ones. In the case of Chant, we can imagine throwing a NoResponseException (I have no guarantee anyone will hear my chant and respond to it, so this is an exception I'm happy with). The easiest way to separate this exception out is to embed it into the return type, requiring a wrapper type for the string.

What does that look like? Here's an option.

IChantResponse Chant(string word, int count, bool? dryRun);
interface IChantResponse {
  bool HasResponse { get; }
  string Response { get; }
}

class SuccessChantResponse {
  public ChantResponse(string response) => Response = response;
  public bool HasResponse => true;
  public string Response { get; }
}

class FailureChantResponse {
  public bool HasResponse => false;
  public string Response => throw new InvalidOperationException();
}

The interface looks good at first but we're now assuming that developers will definitely only access Response after confirming that it exists by checking HasResponse. I think we can do one better.

interface IChantResponse {
  T Handle<T>(Func<string, T> handleResponse, Func<T> handleFailure);
}

class SuccessChantResponse {
  private string _response;
  public SuccessChantResponse(string response) =>
    _response = response;
    
  public T Handle<T>(Func<string, T> handleResponse, Func<T> _) =>
    handleResponse(_response);
}

class FailureChantResponse {
  public T Handle<T>(Func<string, T> _, Func<T> handleFailure) =>
    handleFailure();
}

This perhaps looks much more complicated than it should, but anyone making use of the function now knows exactly how to handle its return result, and doesn't need to bother with a try-catch block — any exceptions Chant now throws are just that.

The code is still terrible though. We haven't covered cases where you want to crash if we got no response. We've required that any logging or metrics work be done inside the actions we pass into the result class. We'll set to work improving these a couple of posts from now.


In the first part of this series I looked at strengthening the types of input variables to cut out the need for input sanitation exceptions. By strengthening the function's return types, we've now cut out the subset of exceptions that are deliberately thrown by the program in response to non-standard events. Here, we merely chanted at someone and got a response, but this example is equivalent to http timeouts, keys not found in dictionaries, values not found in lists, and so on. This means the only exceptions left are those we would actually consider panics, and, like panics, we should allow them to bubble all the way to the top.