Documentation as Design

BusinessMath Development Journey

8 min read

Development Journey Series


The Context

We were implementing IRR (Internal Rate of Return) calculation for BusinessMath. IRR is conceptually simple—find the discount rate where NPV equals zero—but the implementation requires iterative solving with Newton-Raphson method.

I had a working implementation. The tests passed. The calculations were correct.

But I couldn’t document it.

When I tried to write the DocC documentation, I struggled to explain what the parameters meant, when the function would throw errors, and what users should expect. The act of documentation revealed design flaws in the API itself.

That’s when we discovered: If you can’t document it clearly, the API design is wrong.


The Challenge

The traditional workflow puts documentation last:
  1. Design API (maybe)
  2. Implement code
  3. Write tests
  4. Finally: Document what you built
The problem: By step 4, you’ve invested heavily in the implementation. Changing the API now feels expensive. So you write convoluted documentation to explain a poorly designed API instead of fixing the root cause.

With AI generating code quickly, this problem accelerates. AI happily implements whatever you ask for, but it doesn’t push back on bad API design. You get working code with terrible interfaces.

We needed to front-load the design validation.


The Solution

Write complete DocC documentation BEFORE implementing anything.

The Documentation-First Workflow

1. Write the DocC Tutorial First

Before writing any implementation code, write the complete DocC article including:

2. If Documentation Is Hard to Write, Redesign the API

Struggling to document? That’s a signal. The API is confusing. Fix it now while it’s cheap.

3. Use Documentation as AI Specification

Once the documentation reads clearly, give it to AI as the implementation spec. The clearer your docs, the better AI’s implementation.


The Results

Before: Hard to Document

Here’s what AI generated on the first attempt:
// BEFORE: Hard to document
public func calc(_ a: [Double], _ b: Double, _ c: Int) -> Double?
Trying to document this:
/// Calculates… something?
///
/// - Parameter a: An array of… values? Cash flows?
/// - Parameter b: A rate? Or is it a guess?
/// - Parameter c: Maximum… iterations? Or is it periods?
/// - Returns: The result, or nil if… it fails?
Even writing this, I had to guess what the parameters meant. That’s a sign of bad API design.

After: Easy to Document

After redesigning the API with documentation in mind:
// AFTER: Easy to document
/// Calculates the internal rate of return for a series of cash flows.
///
/// The IRR is the discount rate that makes NPV equal to zero.
/// Uses Newton-Raphson method for iterative solving.
///
/// ## Usage Example
///
/// let cashFlows = [-1000, 300, 400, 500]
/// let irr = try calculateIRR(cashFlows: cashFlows)
/// print(irr.percent(1)) // “12.5%”
///
/// - Parameter cashFlows: Array of cash flows, starting with initial investment
/// - Returns: IRR as Double (0.125 = 12.5%)
/// - Throws: FinancialError.convergenceFailure if doesn’t converge
public func calculateIRR(cashFlows: [Double]) throws -> Double
Notice the difference:

What Worked

1. Documentation Revealed IRR Needed Error Handling

The first attempt returned Double? (optional). But when I tried to document this:
/// - Returns: The IRR, or nil if…
I couldn’t finish the sentence. What does nil mean? The documentation revealed the design flaw: we needed typed errors, not ambiguous nil.

Fix:

enum FinancialError: Error {
case convergenceFailure
case invalidCashFlows
}

public func calculateIRR(cashFlows: [Double]) throws -> Double
Now the documentation writes itself:
/// - Throws: FinancialError.convergenceFailure if doesn’t converge after 100 iterations
/// FinancialError.invalidCashFlows if all cash flows are positive

2. Example Showed We Needed Better Formatting

When writing the usage example, I wrote:
let irr = try calculateIRR(cashFlows: cashFlows)
print(irr) // Prints: 0.12456789
Looking at that output, I realized: Users will want percentages, not decimals.

This led to adding format guidance in the documentation:

print(irr.percent(1))  // “12.5%”
Without writing the example first, I wouldn’t have caught this usability issue.

3. AI Implementation Matched Documentation Perfectly

Once the documentation was clear, I gave it to AI with this prompt:
“Implement calculateIRR to match this documentation exactly. Use Newton-Raphson method. The function signature must match what’s documented.”
AI’s implementation: No back-and-forth. No debugging. The documentation was the specification, and AI executed it perfectly.

What Didn’t Work

1. First Attempt at Documentation Was Too Vague

My initial documentation attempt:
/// Calculates IRR for cash flows.
///
/// - Parameter cashFlows: The cash flows
/// - Returns: The IRR
This tells you nothing. What’s the format? What are the units? What can go wrong?

AI implemented it, but not the way I wanted. It made assumptions about default values, convergence tolerance, and error handling that didn’t match my intent.

Fix: Be specific. Include units, formats, edge cases, and examples.


2. Example Initially Didn’t Compile

I wrote the example before implementing the function (good!), but I made a mistake:
// Wrong:
let irr = calculateIRR([-1000, 300, 400, 500]) // Missing label!
When I tried to build the documentation, it failed.

This is actually good! I caught the error in documentation, not in user code. Fixed it immediately:

// Correct:
let irr = try calculateIRR(cashFlows: [-1000, 300, 400, 500])
Lesson: Documentation examples should compile. If they don’t, fix the API before implementing.

The Insight

If you can’t document it clearly, the API design is wrong. Fix it while it’s cheap.

Documentation-first development creates a forcing function:

By writing documentation first, you catch these issues before investing in implementation. Redesigning the API takes 5 minutes. Redesigning after implementation, tests, and integration takes hours.
Key Takeaway: Write DocC before implementation. If the docs are hard to write, the API is wrong. Fix it now, while it’s cheap.

How to Apply This

For your next feature:

1. Write Complete DocC First

2. Check for Red Flags 3. Redesign if Needed 4. Give Documentation to AI 5. Verify Example Compiles

See It In Action

This practice is demonstrated throughout the BusinessMath library:

Technical Examples:

Related Practices:

Common Pitfalls

❌ Pitfall 1: Writing minimal documentation

Problem: “I’ll fill in details later” → Never happens Solution: Write complete docs now. It takes 10 minutes and saves hours.

❌ Pitfall 2: Documenting after implementation

Problem: You’ll rationalize the existing API instead of improving it Solution: Docs first, always. Don’t compromise.

❌ Pitfall 3: Examples that don’t compile

Problem: Users copy broken examples and get frustrated Solution: Build documentation in Xcode, fix compile errors immediately

Discussion

Questions to consider:
  1. How much documentation is “enough” before implementing?
  2. Should every function have an example, or just complex ones?
  3. How do you balance documentation thoroughness with velocity?

Series Progress:


Tagged with: development-process