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:- Design API (maybe)
- Implement code
- Write tests
- Finally: Document what you built
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 FirstBefore writing any implementation code, write the complete DocC article including:
- Overview of what the function does
- Parameter descriptions
- Return value explanation
- Error cases
- Usage examples (that won’t compile yet—that’s okay!)
- See Also references
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:
- Function name is clear:
calculateIRR(notcalc) - Parameters are self-documenting:
cashFlows(nota) - Return type is obvious:
Double(notDouble?) - Errors are explicit:
throws(not returningnil) - Example is compilable and clear
What Worked
1. Documentation Revealed IRR Needed Error Handling
The first attempt returnedDouble? (optional). But when I tried to document this:
/// - Returns: The IRR, or nil if…
I couldn’t finish the sentence.
What does nil mean?
- Didn’t converge after max iterations?
- Invalid cash flows (all positive)?
- Something else?
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:
- ✅ Matched the documented signature exactly
- ✅ Threw the documented errors
- ✅ Handled all edge cases mentioned in docs
- ✅ Passed the example from documentation
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:
- Vague function names become obvious when you try to document them
- Ambiguous parameters can’t be described clearly
- Missing error handling leaves gaps in documentation
- Poor usability shows up in examples
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
- Overview paragraph
- All parameters documented
- Return value explained
- Error cases listed
- Example that shows realistic usage
- Struggling to name parameters clearly?
- Can’t explain what nil means?
- Example is confusing or complex?
- Using words like “various” or “certain cases”?
- Rename parameters for clarity
- Add or remove parameters
- Change return type (optional → throws)
- Simplify the API
- “Implement this function to match the documentation exactly”
- Paste the complete DocC block
- AI will generate code that matches the spec
- Build documentation in Xcode
- Fix any compile errors
- If examples don’t compile, API might still be wrong
See It In Action
This practice is demonstrated throughout the BusinessMath library:Technical Examples:
- Data Table Analysis (Monday): Clear parameter names, typed inputs/outputs
- Financial Ratios (Wednesday): Descriptive function names, documented return types
- Risk Analytics (Friday): Error cases explicitly documented
- Test-First Development (Week 1 Tuesday): Tests validate documented behavior
- Coding Standards (Week 5): Forbidden patterns include undocumented public APIs
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 immediatelyDiscussion
Questions to consider:- How much documentation is “enough” before implementing?
- Should every function have an example, or just complex ones?
- How do you balance documentation thoroughness with velocity?
Series Progress:
- Week: 2/12
- Posts Published: 6/~48
- Methodology Posts: 2/12
- Practices Covered: Test-First, Documentation as Design
Tagged with: development-process