Chuyển đến nội dung chính

Hướng dẫn Code Style

Hướng dẫn này bao gồm các quy ước code được sử dụng trong TablePro. Tuân theo các quy ước này đảm bảo code nhất quán, dễ đọc trong toàn bộ dự án.

Công cụ

TablePro sử dụng các công cụ tự động cho chất lượng code:
Công cụMục đíchFile cấu hình
SwiftLintLinting và phân tích tĩnh.swiftlint.yml
SwiftFormatCode formatting.swiftformat

Chạy các công cụ

# Kiểm tra các vấn đề linting
swiftlint lint

# Tự động sửa các vấn đề linting
swiftlint --fix

# Format code
swiftformat .

# Kiểm tra formatting mà không áp dụng
swiftformat --lint .
Chạy các công cụ này trước khi commit. SwiftLint cũng chạy tự động trong quá trình build Xcode.

Nguyên tắc kiến trúc

Separation of Concerns

  • Giữ logic nghiệp vụ trong models/view models, không trong views
  • Views chỉ nên xử lý presentation
// Tốt: Logic trong ViewModel
class ConnectionViewModel: ObservableObject {
    func validateConnection() -> Bool {
        return !host.isEmpty && port > 0
    }
}

// Không tốt: Logic trong View
struct ConnectionView: View {
    var body: some View {
        Button("Connect") {
            // Không đặt logic validation ở đây
        }
        .disabled(host.isEmpty || port <= 0)
    }
}

Value Types First

Ưu tiên struct hơn class trừ khi cần reference semantics:
// Tốt: Value type cho dữ liệu
struct DatabaseConnection: Codable {
    let id: UUID
    var name: String
    var host: String
}

// Khi nào dùng class: shared state, identity quan trọng
@MainActor
class DatabaseManager: ObservableObject {
    static let shared = DatabaseManager()
}

Immutability

Sử dụng let mặc định; chỉ dùng var khi cần mutation:
// Tốt
let connection = DatabaseConnection(...)
let results = try await driver.execute(query: sql)

// Chỉ dùng var khi cần
var currentPage = 1
currentPage += 1

Quy ước đặt tên

Types

ElementQuy ướcVí dụ
Classes/StructsUpperCamelCaseDatabaseConnection
EnumsUpperCamelCaseDatabaseType
Enum caseslowerCamelCase.postgresql
ProtocolsUpperCamelCaseDatabaseDriver

Functions và Variables

ElementQuy ướcVí dụ
FunctionslowerCamelCaseexecuteQuery()
VariableslowerCamelCaseconnectionString
ConstantslowerCamelCasemaxRetryAttempts
ParameterslowerCamelCasetableName: String

Boolean Properties

Sử dụng prefix is/has/can:
var isConnected: Bool
var hasValidCredentials: Bool
var canExecuteQuery: Bool

Factory Methods

Sử dụng prefix make:
func makeConnection() -> DatabaseConnection
func makeDriver(for type: DatabaseType) -> DatabaseDriver

Formatting

Indentation

  • 4 spaces (không bao giờ dùng tabs)
  • Cấu hình editor để chuyển tabs thành spaces

Độ dài dòng

  • Tối đa 120 ký tự mỗi dòng
  • Ngắt dòng dài để dễ đọc
// Tốt: Ngắt dòng để dễ đọc
let result = try await driver.executeParameterized(
    query: "SELECT * FROM users WHERE email = ?",
    parameters: [email]
)

// Tránh: Dòng dài đơn
let result = try await driver.executeParameterized(query: "SELECT * FROM users WHERE email = ?", parameters: [email])

Braces

K&R style - opening brace trên cùng dòng:
// Tốt
if condition {
    doSomething()
} else {
    doSomethingElse()
}

// Không tốt
if condition
{
    doSomething()
}

Spacing

// Khoảng trắng xung quanh operators
let sum = a + b
let isValid = count > 0 && name.isEmpty == false

// Không có khoảng trắng cho ranges
for i in 0..<10 { }

// Khoảng trắng sau colon trong khai báo type
var name: String
func connect(host: String, port: Int)

Access Control

Chỉ định rõ ràng

Luôn chỉ định access modifiers:
// Tốt: Access rõ ràng
private var connectionPool: [Connection] = []
internal func processResult(_ result: QueryResult)
public func connect() async throws

// Tránh: Implicit internal
var connectionPool: [Connection] = []  // Implicit internal

Ưu tiên Private

Sử dụng access hạn chế nhất có thể:
class DatabaseManager {
    // Public API
    public func connect() async throws { ... }

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

Extension Access

Chỉ định access trên extension, không phải các thành viên riêng lẻ:
// Tốt
public extension NSEvent {
    var semanticKeyCode: KeyCode? { ... }
}

// Tránh
extension NSEvent {
    public var semanticKeyCode: KeyCode? { ... }
}

Optionals

Tránh Force Unwrapping

Không bao giờ dùng ! để unwrap:
// Tốt
if let connection = connection {
    use(connection)
}

guard let result = result else { return }

// Không tốt
let connection = connection!  // Crash nếu nil

Safe Casting

Sử dụng conditional casting:
// Tốt
if let value = param as? SQLFunctionLiteral {
    return value.property
}

// Không tốt
let value = param as! SQLFunctionLiteral  // Crash nếu sai type

Guard cho Early Exit

Sử dụng guard để giảm nesting:
// Tốt
func processResult(_ result: QueryResult?) -> [Row] {
    guard let result = result else { return [] }
    guard result.rowCount > 0 else { return [] }

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

// Tránh nesting sâu
func processResult(_ result: QueryResult?) -> [Row] {
    if let result = result {
        if result.rowCount > 0 {
            return result.rows.map { transform($0) }
        }
    }
    return []
}

Collections

Ưu tiên Semantic Methods

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

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

Xóa Unused Enumeration

// Tốt
items.map { item in item.value }
items.map(\.value)

// Tránh (nếu index không được dùng)
items.enumerated().map { _, item in item.value }

Closures

Trailing Closure Syntax

Sử dụng trailing closures khi tham số cuối là closure:
// Tốt
items.filter { $0.isActive }
    .map { $0.name }

// Tránh
items.filter({ $0.isActive })
    .map({ $0.name })

Implicit Returns

Sử dụng implicit returns cho single-expression closures:
// Tốt
items.map { $0.name }
items.filter { $0.isActive }

// Cũng tốt khi rõ ràng hơn
items.map { item in
    item.name
}

Unused Closure Arguments

Sử dụng _ cho các tham số không dùng:
// Tốt
button.onTap { _ in
    handleTap()
}

// Tránh
button.onTap { event in  // event không được dùng
    handleTap()
}

Comments

Khi nào Comment

// Tốt: Giải thích "tại sao", không phải "cái gì"
// Chúng ta dùng delay 1.5 giây để đảm bảo SSH tunnel được thiết lập
// trước khi thử kết nối database
try await Task.sleep(nanoseconds: 1_500_000_000)

// Tránh: Comments hiển nhiên
// Lấy tên user
let name = user.name

Documentation Comments

Sử dụng /// cho public APIs:
/// Thực thi SQL query và trả về kết quả.
///
/// - Parameter query: SQL query cần thực thi
/// - Returns: `QueryResult` chứa rows và columns
/// - Throws: `DatabaseError` nếu query thất bại
func execute(query: String) async throws -> QueryResult

MARK Comments

Tổ chức code với 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 { ... }

Đặc Thù SwiftUI

Cấu trúc View

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 {
        // ...
    }
}

Thứ tự Property Wrappers

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
}

Giới hạn

Giới hạn SwiftLint

MetricWarningError
Độ dài Function body160 dòng250 dòng
Độ dài Type body1100 dòng1500 dòng
Độ dài File1200 dòng1800 dòng
Cyclomatic complexity4060

Xử lý File dài

Trích xuất extensions vào các file riêng:
MainContentCoordinator.swift
Extensions
MainContentCoordinator+RowOperations.swift
MainContentCoordinator+Pagination.swift
MainContentCoordinator+Filtering.swift

Tài nguyên

Bước tiếp theo