Code Style Guide
This guide covers the coding conventions used in TablePro. Following these conventions ensures consistent, readable code across the project.
TablePro uses automated tools for code quality:
Tool Purpose Config File SwiftLint Linting and static analysis .swiftlint.ymlSwiftFormat Code formatting .swiftformat
# 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
Element Convention Example Classes/Structs UpperCamelCase DatabaseConnectionEnums UpperCamelCase DatabaseTypeEnum cases lowerCamelCase .postgresqlProtocols UpperCamelCase DatabaseDriver
Functions and Variables
Element Convention Example Functions lowerCamelCase executeQuery()Variables lowerCamelCase connectionStringConstants lowerCamelCase maxRetryAttemptsParameters lowerCamelCase tableName: 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
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 ()
}
// 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
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
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
Metric Warning Error Function body length 160 lines 250 lines Type body length 1100 lines 1500 lines File length 1200 lines 1800 lines Cyclomatic complexity 40 60
Handling Long Files
Extract extensions into separate files:
MainContentCoordinator.swift
MainContentCoordinator+RowOperations.swift MainContentCoordinator+Pagination.swift MainContentCoordinator+Filtering.swift
Resources
Next Steps