precondition() vs types

I have a bit of a design conundrum. I have an API that looks like this:

public func next(index: ContentType.Index?) throws -> TokenizerResult<ContentType>? {
    precondition(rules.count > 0, "There are no rules specified for your tokenizer.")
    // ...
}

This belongs on a protocol Tokenizer and a default implementation is provided in an extension. The rules are static per type, at least in the default case.

So… the problem? I cannot write a test for this code because precondition fails with an uncatchable runtime exception. What I really want is a type ArrayOfOneOrMore<Element>. However, this requires a lot of code and method forwarding to work; I don't want to do that.

It seems my options are:

  1. Be ok with not being able to write a test for this.
  2. Relax the condition, but this hides a logical implementation error.
  3. Do all of the work to implement ArrayOfOneOrMore<Element>.

Is there another option I'm missing?

Update July 9th, 2015

So I've been thinking about this some more. There is a workaround that can be done: change precondition() to assert() and compile out the assertions in a new Debug-unchecked build for testing. This keeps all of the good debugging symbols and optimizer changes at bay while still allowing for testing your code flow.

Steps to get this to work:

  1. Create a new build config Debug-unchecked
  2. Change the Other Swift Flags (OTHER_SWIFT_FLAGS) compiler options to -assert-config Release
  3. Change precondition() to assert()
  4. Update your code to handle the code path for the error state

    public func next(index: ContentType.Index?) throws -> TokenizerResult?

    assert(!rules.isEmpty, "There are no rules specified for your tokenizer.")
    		if rules.isEmpty { throw TokenizerError.NoTokenizerRulesPresent }  
    		  // ...
    		

    }

This has the benefit of raising the error immediately on failure for debug builds, providing a testing path, and still providing the error in a catchable way in release builds.

Of course, if I do this a lot, I'll probably just do this:

public func next(index: ContentType.Index?) throws -> TokenizerResult<ContentType>? {
      try assert(!rules.isEmpty, "There are no rules specified for your tokenizer.", TokenizerError.NoTokenizerRulesPresent)
      // ...
}

And create these helpers that I just use all the time:

func assert(@autoclosure fn: () -> Bool, _ error: ErrorType) throws {
    return try assert(fn, "", error)
}

func assert(@autoclosure fn: () -> Bool, _ message: String, _ error: ErrorType) throws {
    let result = fn()
    assert(result, message)
    if !result { throw error }
}

At least with these helpers it's no extra work for me. The biggest drawback I see is that it requires compiling my code again, which could suck.

precondition() vs types