Skip to main content

Code Style Guide

This guide covers the coding conventions used in TablePro. Following these conventions ensures consistent, readable code across the project.

Tools

TablePro uses automated tools for code quality:
ToolPurposeConfig File
SwiftLintLinting and static analysis.swiftlint.yml
SwiftFormatCode formatting.swiftformat

Running Tools

# Check for linting issues
swiftlint lint

# Auto-fix linting issues
swiftlint --fix

# Format code
swiftformat .

# Check formatting without applying
swiftformat --lint .
Run these tools before committing. SwiftLint also runs automatically during Xcode builds.

Architecture Principles

Separation of Concerns

  • Keep business logic in models/view models, not in views
  • Views should only handle presentation
// Good: Logic in ViewModel
class ConnectionViewModel: ObservableObject {
    func validateConnection() -> Bool {
        return !host.isEmpty && port > 0
    }
}

// Bad: Logic in View
struct ConnectionView: View {
    var body: some View {
        Button("Connect") {
            // Don't put validation logic here
        }
        .disabled(host.isEmpty || port <= 0)
    }
}

Value Types First

Prefer struct over class unless reference semantics are needed:
// Good: Value type for data
struct DatabaseConnection: Codable {
    let id: UUID
    var name: String
    var host: String
}

// When to use class: shared state, identity matters
@MainActor
class DatabaseManager: ObservableObject {
    static let shared = DatabaseManager()
}

Immutability

Use let by default; only use var when mutation is required:
// Good
let connection = DatabaseConnection(...)
let results = try await driver.execute(query: sql)

// Only var when needed
var currentPage = 1
currentPage += 1

Naming Conventions

Types

ElementConventionExample
Classes/StructsUpperCamelCaseDatabaseConnection
EnumsUpperCamelCaseDatabaseType
Enum caseslowerCamelCase.postgresql
ProtocolsUpperCamelCaseDatabaseDriver

Functions and Variables

ElementConventionExample
FunctionslowerCamelCaseexecuteQuery()
VariableslowerCamelCaseconnectionString
ConstantslowerCamelCasemaxRetryAttempts
ParameterslowerCamelCasetableName: String

Boolean Properties

Use is/has/can prefixes:
var isConnected: Bool
var hasValidCredentials: Bool
var canExecuteQuery: Bool

Factory Methods

Use make prefix:
func makeConnection() -> DatabaseConnection
func makeDriver(for type: DatabaseType) -> DatabaseDriver

Formatting

Indentation

  • 4 spaces (never tabs)
  • Configure your editor to convert tabs to spaces

Line Length

  • Maximum 120 characters per line
  • Break long lines for readability
// Good: Line breaks for readability
let result = try await driver.executeParameterized(
    query: "SELECT * FROM users WHERE email = ?",
    parameters: [email]
)

// Avoid: Long single line
let result = try await driver.executeParameterized(query: "SELECT * FROM users WHERE email = ?", parameters: [email])

Braces

K&R style - opening brace on same line:
// Good
if condition {
    doSomething()
} else {
    doSomethingElse()
}

// Bad
if condition
{
    doSomething()
}

Spacing

// Space around operators
let sum = a + b
let isValid = count > 0 && name.isEmpty == false

// No space for ranges
for i in 0..<10 { }

// Space after colon in type declarations
var name: String
func connect(host: String, port: Int)

Access Control

Specify Explicitly

Always specify access modifiers:
// Good: Explicit access
private var connectionPool: [Connection] = []
internal func processResult(_ result: QueryResult)
public func connect() async throws

// Avoid: Implicit internal
var connectionPool: [Connection] = []  // Implicit internal

Prefer Private

Use the most restrictive access that works:
class DatabaseManager {
    // Public API
    public func connect() async throws { ... }

    // Internal helpers
    private func validateConnection() -> Bool { ... }
    private var activeDriver: DatabaseDriver?
}

Extension Access

Specify access on the extension, not individual members:
// Good
public extension NSEvent {
    var semanticKeyCode: KeyCode? { ... }
}

// Avoid
extension NSEvent {
    public var semanticKeyCode: KeyCode? { ... }
}

Optionals

Avoid Force Unwrapping

Never use ! for unwrapping:
// Good
if let connection = connection {
    use(connection)
}

guard let result = result else { return }

// Bad
let connection = connection!  // Crash if nil

Safe Casting

Use conditional casting:
// Good
if let value = param as? SQLFunctionLiteral {
    return value.property
}

// Bad
let value = param as! SQLFunctionLiteral  // Crash if wrong type

Guard for Early Exit

Use guard to reduce nesting:
// Good
func processResult(_ result: QueryResult?) -> [Row] {
    guard let result = result else { return [] }
    guard result.rowCount > 0 else { return [] }

    return result.rows.map { transform($0) }
}

// Avoid deep nesting
func processResult(_ result: QueryResult?) -> [Row] {
    if let result = result {
        if result.rowCount > 0 {
            return result.rows.map { transform($0) }
        }
    }
    return []
}

Collections

Prefer Semantic Methods

// Good
if items.isEmpty { }
if items.contains(target) { }
if let first = items.first(where: { $0.isValid }) { }

// Avoid
if items.count == 0 { }
if items.filter({ $0 == target }).count > 0 { }
if let first = items.filter({ $0.isValid }).first { }

Remove Unused Enumeration

// Good
items.map { item in item.value }
items.map(\.value)

// Avoid (if index not used)
items.enumerated().map { _, item in item.value }

Closures

Trailing Closure Syntax

Use trailing closures when the last parameter is a closure:
// Good
items.filter { $0.isActive }
    .map { $0.name }

// Avoid
items.filter({ $0.isActive })
    .map({ $0.name })

Implicit Returns

Use implicit returns for single-expression closures:
// Good
items.map { $0.name }
items.filter { $0.isActive }

// Also good when clearer
items.map { item in
    item.name
}

Unused Closure Arguments

Use _ for unused arguments:
// Good
button.onTap { _ in
    handleTap()
}

// Avoid
button.onTap { event in  // event not used
    handleTap()
}

Comments

When to Comment

// Good: Explain "why", not "what"
// We use a 1.5 second delay to ensure the SSH tunnel is established
// before attempting the database connection
try await Task.sleep(nanoseconds: 1_500_000_000)

// Avoid: Obvious comments
// Get the user's name
let name = user.name

Documentation Comments

Use /// for public APIs:
/// Executes a SQL query and returns the results.
///
/// - Parameter query: The SQL query to execute
/// - Returns: A `QueryResult` containing rows and columns
/// - Throws: `DatabaseError` if the query fails
func execute(query: String) async throws -> QueryResult

MARK Comments

Organize code with MARK comments:
// MARK: - Properties

private var connection: DatabaseConnection
private var driver: DatabaseDriver?

// MARK: - Lifecycle

init(connection: DatabaseConnection) {
    self.connection = connection
}

// MARK: - Public Methods

func connect() async throws { ... }

// MARK: - Private Helpers

private func validateConnection() -> Bool { ... }

SwiftUI Specific

View Structure

struct ContentView: View {
    // MARK: - Properties

    @StateObject private var viewModel = ContentViewModel()
    @State private var isLoading = false

    // MARK: - Body

    var body: some View {
        VStack {
            headerView
            contentView
            footerView
        }
    }

    // MARK: - Subviews

    private var headerView: some View {
        Text("Header")
    }

    private var contentView: some View {
        // ...
    }
}

Property Wrappers Order

struct MyView: View {
    // Environment
    @Environment(\.dismiss) private var dismiss
    @EnvironmentObject private var appState: AppState

    // State Objects
    @StateObject private var viewModel = MyViewModel()

    // State
    @State private var isEditing = false
    @Binding var selection: Item?

    // Regular properties
    let title: String
}

Limits

SwiftLint Limits

MetricWarningError
Function body length160 lines250 lines
Type body length1100 lines1500 lines
File length1200 lines1800 lines
Cyclomatic complexity4060

Handling Long Files

Extract extensions into separate files:
MainContentCoordinator.swift
Extensions
MainContentCoordinator+RowOperations.swift
MainContentCoordinator+Pagination.swift
MainContentCoordinator+Filtering.swift

Resources

Next Steps