Skip to main content

Development Workflow

Руководство по процессу разработки новых функций в Thrust.

Настройка окружения

Первый запуск

# 1. Клонировать репозиторий
git clone https://github.com/Andrei-Kondrykau/thrust.git
cd thrust

# 2. Запустить быструю настройку
./Scripts/QUICK_SETUP.sh

# 3. Открыть проект
open Thrust.xcodeproj

Требования

  • Xcode 15.0+
  • iOS 17.0+ (deployment target)
  • macOS 14.0+ (для разработки)
  • Swift 5.9+

Secrets

Создайте Thrust/Config/Secrets.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>GoCardlessSecretID</key>
    <string>your_secret_id</string>
    <key>GoCardlessSecretKey</key>
    <string>your_secret_key</string>
    <key>MoralisAPIKey</key>
    <string>your_moralis_key</string>
</dict>
</plist>

Git Workflow

Ветки

Основные ветки:
  • main - production код
  • develop - development код
  • feature/* - новые функции
  • bugfix/* - исправления багов
  • hotfix/* - срочные исправления

Создание feature

# 1. Создать ветку от develop
git checkout develop
git pull origin develop
git checkout -b feature/add-budget-templates

# 2. Разработка
# ... делаете изменения ...

# 3. Commit
git add .
git commit -m "feat: add budget templates functionality"

# 4. Push
git push origin feature/add-budget-templates

# 5. Создать Pull Request в develop

Commit Messages

Используйте Conventional Commits:
feat: добавить новую функцию
fix: исправить баг
docs: обновить документацию
style: форматирование кода
refactor: рефакторинг
test: добавить тесты
chore: обновить зависимости
Примеры:
feat: add budget rollover functionality
fix: crash when deleting account with transactions
docs: update API reference for LeapCFOService
refactor: extract transaction categorization logic
test: add unit tests for SafeToSpendEngine

Разработка новой функции

Шаг 1: Планирование

  1. Создайте issue в GitHub
  2. Опишите функцию и требования
  3. Обсудите с командой
  4. Получите approval

Шаг 2: Дизайн

  1. Определите где функция живет:
    • Новый модуль в Features/?
    • Расширение существующего модуля?
    • Новый менеджер в Managers/?
  2. Спроектируйте модели данных:
    • Нужна ли новая @Model?
    • Изменения в существующих моделях?
    • Миграция данных?
  3. Определите UI:
    • Новые views
    • Изменения в навигации
    • Интеграция с существующим UI

Шаг 3: Реализация

Создание модели:
// Thrust/Models/BudgetTemplate.swift
import SwiftData
import Foundation

@Model
final class BudgetTemplate {
    var id: UUID = UUID()
    var name: String
    var amount: Double
    var categories: [Category]
    var period: BudgetPeriod
    var createdAt: Date = Date()
    
    init(name: String, amount: Double, categories: [Category], period: BudgetPeriod) {
        self.name = name
        self.amount = amount
        self.categories = categories
        self.period = period
    }
}
Создание View:
// Thrust/Features/Budgeting/BudgetTemplatesView.swift
import SwiftUI
import SwiftData

struct BudgetTemplatesView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var templates: [BudgetTemplate]
    
    var body: some View {
        List {
            ForEach(templates) { template in
                BudgetTemplateRow(template: template)
            }
        }
        .navigationTitle("Budget Templates")
    }
}
Создание ViewModel (если нужна сложная логика):
// Thrust/Features/Budgeting/BudgetTemplatesViewModel.swift
import SwiftUI
import SwiftData

@Observable
final class BudgetTemplatesViewModel {
    var templates: [BudgetTemplate] = []
    var isLoading = false
    
    func loadTemplates(context: ModelContext) async {
        isLoading = true
        defer { isLoading = false }
        
        let descriptor = FetchDescriptor<BudgetTemplate>(
            sortBy: [SortDescriptor(\\.createdAt, order: .reverse)]
        )
        
        templates = (try? context.fetch(descriptor)) ?? []
    }
}

Шаг 4: Интеграция

Добавить в навигацию:
// Thrust/Features/Budgeting/BudgetListView.swift
.toolbar {
    ToolbarItem(placement: .navigationBarTrailing) {
        Menu {
            Button("Create Budget", systemImage: "plus") {
                // ...
            }
            Button("Templates", systemImage: "doc.on.doc") {
                showTemplates = true
            }
        } label: {
            Image(systemName: "ellipsis.circle")
        }
    }
}
.sheet(isPresented: $showTemplates) {
    BudgetTemplatesView()
}
Добавить в Environment (если нужен менеджер):
// Thrust/Core/Thrust.swift
@main
struct ThrustApp: App {
    @State private var budgetTemplateManager = BudgetTemplateManager()
    
    var body: some Scene {
        WindowGroup {
            MainView()
                .environment(budgetTemplateManager)
        }
    }
}

Шаг 5: Тестирование

Unit тесты:
// Thrust.appTests/BudgetTemplateTests.swift
import XCTest
@testable import Thrust

final class BudgetTemplateTests: XCTestCase {
    func testCreateTemplate() {
        let template = BudgetTemplate(
            name: "Monthly Groceries",
            amount: 20000,
            categories: [],
            period: .monthly
        )
        
        XCTAssertEqual(template.name, "Monthly Groceries")
        XCTAssertEqual(template.amount, 20000)
    }
}
UI тесты:
// Thrust.appUITests/BudgetTemplatesUITests.swift
import XCTest

final class BudgetTemplatesUITests: XCTestCase {
    func testCreateTemplateFlow() {
        let app = XCUIApplication()
        app.launch()
        
        // Navigate to templates
        app.buttons["Budgets"].tap()
        app.buttons["Templates"].tap()
        
        // Create template
        app.buttons["Create Template"].tap()
        app.textFields["Name"].tap()
        app.textFields["Name"].typeText("Test Template")
        
        app.buttons["Save"].tap()
        
        // Verify
        XCTAssertTrue(app.staticTexts["Test Template"].exists)
    }
}

Шаг 6: Документация

  1. Добавьте комментарии к коду
  2. Обновите README если нужно
  3. Добавьте в документацию Mintlify
  4. Обновите CHANGELOG

Шаг 7: Code Review

  1. Создайте Pull Request
  2. Заполните описание:
    • Что изменено
    • Зачем
    • Как тестировать
    • Screenshots (для UI)
  3. Запросите review у команды
  4. Исправьте замечания
  5. Получите approval
  6. Merge в develop

Проверка качества

Перед commit

# Запустить проверку качества
./Scripts/quality-check-fintech.sh
Проверяет:
  • SwiftLint warnings
  • Compilation errors
  • Unit tests
  • Code coverage
  • Security issues

Перед release

# Запустить полную проверку
./Scripts/release_check.sh
Проверяет:
  • Все тесты (unit + UI)
  • Performance tests
  • Memory leaks
  • Локализации
  • Assets
  • Entitlements

Best Practices

Код

1. Используйте существующие компоненты ❌ Плохо:
VStack {
    HStack {
        Image(systemName: "dollarsign.circle")
        Text("Balance")
    }
    Text("$1,234.56")
}
✅ Хорошо:
UnifiedListRow(
    icon: .system("dollarsign.circle"),
    title: "Balance",
    value: "$1,234.56"
)
2. Следуйте Design System ❌ Плохо:
Text("Title")
    .font(.system(size: 24, weight: .bold))
    .foregroundColor(.blue)
✅ Хорошо:
Text("Title")
    .appTextStyle(.screenTitle)
3. Обрабатывайте ошибки ❌ Плохо:
try modelContext.save()
✅ Хорошо:
do {
    try modelContext.save()
} catch {
    AppLogger.error("Failed to save: \\(error)", category: .data)
    // Show error to user
}
4. Используйте async/await ❌ Плохо:
DispatchQueue.main.async {
    self.data = fetchData()
}
✅ Хорошо:
Task {
    data = await fetchData()
}

SwiftData

1. Используйте @Query для чтения
@Query(sort: \\.date, order: .reverse) 
private var transactions: [Transaction]
2. Используйте FetchDescriptor для сложных запросов
let predicate = #Predicate<Transaction> { tx in
    tx.type == .expense && 
    tx.date >= startDate &&
    tx.date <= endDate
}

let descriptor = FetchDescriptor<Transaction>(
    predicate: predicate,
    sortBy: [SortDescriptor(\\.date, order: .reverse)]
)

let transactions = try modelContext.fetch(descriptor)
3. Batch операции
// Удаление множества объектов
try modelContext.delete(model: Transaction.self, where: predicate)

UI/UX

1. Accessibility
Button("Delete") { }
    .accessibilityLabel("Delete transaction")
    .accessibilityHint("Removes this transaction permanently")
2. Loading states
if isLoading {
    ProgressView()
} else {
    ContentView()
}
3. Empty states
if transactions.isEmpty {
    EmptyStateView(
        icon: "tray",
        title: "No Transactions",
        message: "Add your first transaction to get started"
    )
}
4. Error handling
.alert("Error", isPresented: $showError) {
    Button("OK") { }
} message: {
    Text(errorMessage)
}

Debugging

Логирование

AppLogger.debug("User tapped button", category: .ui)
AppLogger.info("Fetching transactions", category: .network)
AppLogger.warning("Low memory", category: .performance)
AppLogger.error("Failed to save: \\(error)", category: .data)

Breakpoints

Используйте symbolic breakpoints для:
  • UIViewAlertForUnsatisfiableConstraints - Auto Layout issues
  • objc_exception_throw - Objective-C exceptions

Instruments

Регулярно проверяйте:
  • Leaks - утечки памяти
  • Allocations - использование памяти
  • Time Profiler - производительность
  • Network - сетевые запросы

CI/CD

Xcode Cloud

Автоматические проверки при каждом PR:
  • Build
  • Unit tests
  • UI tests
  • SwiftLint
  • Code coverage

Релиз

  1. Обновите версию в Thrust-app-Info.plist
  2. Обновите CHANGELOG.md
  3. Создайте tag: git tag v1.2.0
  4. Push tag: git push origin v1.2.0
  5. Xcode Cloud автоматически создаст build для TestFlight

Следующие шаги