Skip to main content

Extreme Programming (XP) Reference

Extreme Programming concepts — values, practices, and roles — mapped to an iOS development context with what, why, and how for each.

What is Extreme Programming?

Extreme Programming (XP) is an agile software development methodology created by Kent Beck in the late 1990s. It takes proven good practices — testing, code review, iteration — and turns the dial up to the maximum. If code review is good, review code constantly (pair programming). If testing is good, test everything before you write it (TDD). If integration is good, integrate many times a day (CI).

From an iOS perspective, XP gives you a disciplined, team-scale answer to the question: “How do we ship high-quality features on a two-week sprint without the codebase rotting under us?”


Values

XP is built on five values. Everything else flows from these.

ValueWhatWhy (iOS context)
CommunicationTeam members talk constantly — no silosPrevents the “two engineers implementing the same networking layer” disaster
SimplicityDo the simplest thing that could possibly workAvoids over-engineering HealthKit integration before the feature even ships
FeedbackShort loops — tests, builds, and user feedback dailyCatches a broken SwiftUI layout in minutes, not after code review
CourageRefactor aggressively, delete dead code, tell the truth on estimatesLets you remove a 3,000-line legacy UIKit controller without fear
RespectEveryone’s contribution matters; no rockstar cultureEnsures the junior who found the memory leak gets the same voice as the tech lead

Core Practices

Planning Game

Detail
WhatCollaborative iteration planning where engineers estimate stories and the business prioritises them
WhyAligns what’s valuable (the App Store feature) with what’s feasible (the two-week sprint)
How (iOS)Break work into user stories: “As a user I can filter my workout history by date.” Engineers estimate in story points based on complexity — SwiftUI view vs. Core Data migration — not hours. Product picks the order; engineers pick the volume per sprint.
Story: User can export workouts as CSV
  Tasks:
    - Add Export button to WorkoutListView (1 pt)
    - Implement CSVExporter with tests (2 pts)
    - Wire UIActivityViewController share sheet (1 pt)
    - UI test for the happy path (1 pt)

Small Releases

Detail
WhatShip to real users frequently — weeks, not months
WhyApp Store rejection or a bad UX discovery is cheaper to fix in week two than week twelve
How (iOS)Use TestFlight as your continuous delivery channel. Every sprint produces a TestFlight build. Separate feature flags (via a FeatureFlags enum or a remote config like Firebase Remote Config) so unfinished work ships to the binary without being visible to users.
enum Feature {
    static var exportEnabled: Bool {
        RemoteConfig.shared.bool(forKey: "export_enabled")
    }
}

// In WorkoutListView
if Feature.exportEnabled {
    Button("Export") { viewModel.export() }
}

Test-Driven Development (TDD)

Detail
WhatWrite a failing test first, then write the minimum code to make it pass, then refactor
WhyForces you to design the API before the implementation — your CSVExporter interface becomes clear before a single line of production code exists
How (iOS)Use Swift Testing for unit tests and XCTest / XCUITest for integration and UI tests. Follow red-green-refactor strictly.
// Red: write the failing test first
@Test func exportProducesValidCSVHeader() {
    let exporter = CSVExporter()
    let output = exporter.export(workouts: [])
    #expect(output.hasPrefix("Date,Duration,Calories"))
}

// Green: write the minimum implementation
struct CSVExporter {
    func export(workouts: [Workout]) -> String {
        var lines = ["Date,Duration,Calories"]
        for w in workouts {
            lines.append("\(w.date),\(w.duration),\(w.calories)")
        }
        return lines.joined(separator: "\n")
    }
}

// Refactor: extract DateFormatter, handle empty state, etc.

Pair Programming

Detail
WhatTwo engineers share one machine — one drives (writes code), one navigates (reviews, thinks ahead)
WhyCatches bugs in real time, spreads architectural knowledge, and eliminates the knowledge silo where only one person understands the sync engine
How (iOS)Rotate pairs daily. Use screen sharing in Xcode or VS Code with Live Share for remote pairs. The navigator catches things like “that @MainActor isolation is going to cause a data race” before the driver even finishes the line.
Pair rotation (2-week sprint):
  Week 1, Mon-Wed: Alice + Bob → Core Data migration
  Week 1, Thu-Fri: Alice + Carol → SwiftUI views
  Week 2:          Bob + Carol → Networking layer

Continuous Integration

Detail
WhatEvery engineer integrates their branch into main at least once per day; automated build and tests run on every push
WhyMakes merge conflicts and integration bugs surface in hours, not the night before a release
How (iOS)Use GitHub Actions + Xcode Cloud (or Fastlane on a Mac runner). A typical CI pipeline: lint (SwiftLint), build for simulator, run unit tests, run UI tests on iPhone 15 simulator. Fail fast — if lint fails, don’t bother building.
# .github/workflows/ci.yml (simplified)
jobs:
  test:
    runs-on: macos-15
    steps:
      - uses: actions/checkout@v4
      - name: Lint
        run: swiftlint lint --strict
      - name: Build & Test
        run: |
          xcodebuild test \
            -scheme MyApp \
            -destination 'platform=iOS Simulator,name=iPhone 16' \
            -resultBundlePath TestResults.xcresult

Collective Code Ownership

Detail
WhatAny engineer can improve any part of the codebase at any time — no “owner” of a file
WhyPrevents the situation where only one person can touch the payment module and that person is on holiday
How (iOS)Enforce this culturally: no @author comments, no CODEOWNERS that block PRs. Document intent in commit messages and PR descriptions, not in file headers. Pair programming naturally spreads ownership.

Refactoring

Detail
WhatContinuously improve the design of existing code without changing its external behaviour
WhyWithout refactoring, the codebase accumulates technical debt and every new feature costs more than the last
How (iOS)Refactor before adding a feature, not after. If you’re about to add offline support and the networking layer is a 600-line APIManager singleton, extract NetworkClient and RequestBuilder first, covered by tests. Then add the offline layer.
// Before: monolithic singleton
class APIManager {
    static let shared = APIManager()
    func fetchUser() async throws -> User { ... }
    func fetchWorkouts() async throws -> [Workout] { ... }
    // 40 more methods...
}

// After: focused, testable types
struct NetworkClient {
    func send<T: Decodable>(_ request: URLRequest) async throws -> T { ... }
}

struct UserService {
    private let client: NetworkClient
    func fetchUser() async throws -> User { ... }
}

Simple Design

Detail
WhatThe code does exactly what’s needed — no speculative abstractions, no “we might need this later” generics
WhyEvery premature abstraction is complexity you pay for in every future change
How (iOS)Pass the four rules: the code runs all tests, expresses intent clearly, has no duplication, and has the fewest possible classes and methods. Don’t add a BaseViewModel protocol until you have three ViewModels that actually share behaviour.

Coding Standards

Detail
WhatA shared, enforced style guide so all code looks like it was written by one person
WhyCollective ownership only works when you can read anyone’s code without a context switch
How (iOS)Use SwiftLint with a shared .swiftlint.yml checked into the repo. Enforce formatting with swift-format. Codify naming conventions: ViewModels are @Observable, services are structs, types follow Swift API Design Guidelines.
# .swiftlint.yml
excluded:
  - Pods
  - .build
opt_in_rules:
  - force_unwrapping
  - implicit_return
  - prefer_self_in_static_references
line_length: 120

System Metaphor

Detail
WhatA shared story — a simple analogy — that explains how the system works to anyone on the team
WhyKeeps naming consistent and gives new engineers a mental model on day one
How (iOS)Example: “The app is a gym logbook. A WorkoutSession is one page, a Set is one row on that page, and the SyncEngine is the person who photocopies the logbook to the cloud every night.” These metaphors drive module names and help everyone use the same vocabulary.

Sustainable Pace

Detail
WhatNo heroics — the team works at a pace it can sustain indefinitely
WhyCrunch kills code quality: tired engineers write bugs, skip tests, and take shortcuts that haunt the codebase for years
How (iOS)Treat overtime as a signal that planning was wrong, not a solution. If the sprint is consistently too full, reduce velocity commitments. Use CI and TDD to keep “getting it done” the same as “getting it done right.”

Roles

RoleResponsibilityiOS Example
CustomerDefines user stories, sets priorities, answers questions dailyProduct manager or designer defining what “offline-first” means for users
DeveloperEstimates, designs, implements, and tests storiesiOS engineer building the sync engine
CoachGuides the team on XP practices, removes impedimentsTech lead or agile coach ensuring TDD is followed and CI stays green
TrackerMonitors velocity and warns when commitments are at riskScrum master tracking story-point burn-down

XP Practices at a Glance

PracticeFrequencyiOS Tooling
TDDEvery feature, every bug fixSwift Testing, XCTest
Pair programmingDaily, rotating pairsXcode, VS Code Live Share
CIEvery push to any branchGitHub Actions, Xcode Cloud
Small releasesEvery sprint (1-2 weeks)TestFlight, feature flags
RefactoringBefore adding each featureXcode refactor tools, tests as safety net
Planning gameStart of every sprintLinear, Jira, GitHub Issues
Coding standardsEnforced on every commitSwiftLint, swift-format, pre-commit hooks
Collective ownershipAlwaysNo CODEOWNERS blocking, pair rotation
Sustainable paceAlwaysSprint retrospectives, velocity tracking