PUBLIC OBJECT

Strict vs. Forgiving APIs

Suppose it's the early 1990's and you're James Gosling implementing String.substring(int, int) for the first time. What should happen when the index arguments are out-of-range? Should these tests pass? Or throw?

  public void testSubstring() {
    assertEquals("class", "superclass".substring(5, 32));
    assertEquals("super", "superclass".substring(-2, 5));
    assertEquals("", "superclass".substring(20, 24));
    assertEquals("superclass", "superclass".substring(10, 0));
  }

Forgiving APIs

In a forgiving API, these tests pass. The implementation would recognize the out-of-range indices and correct for them. Benefits of forgiving APIs:

  • Fault-tolerant. An off-by-one mistake won't bring a production system to its knees.
  • Easier to code against. If you don't know what to use for a given argument, just pass null and the implementation will do something reasonable.

Strict APIs

In a strict APIs, the out-of-range arguments to substring are forbidden and the method throws an IllegalArgumentException. Benefits of strict APIs:

  • Fail-fast. An off-by-one mistake will be caught in unit tests, if they exist.
  • Easier to maintain. By limiting the number of valid inputs, there's less behaviour to maintain and test.
  • More Predictable. Mapping invalid inputs to behaviour is an artform. In the example, should substring(10, 0) return the empty string? Or "superclass"? What would the caller expect?

For maintainability, I almost always prefer strict APIs. I like to think of the classes in my code as the gears in a fine Swiss watch. Everything fits together tightly, with firm constraints on both the inputs and the outputs. I can refactor with confidence because the system simply won't work if I've introduced problems into it. With a forgiving API, I could introduce bugs and not find out about them until much later.