<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>devops</title>
    <link>https://write.rpod.club/devops/</link>
    <description></description>
    <pubDate>Tue, 20 Oct 2026 05:23:36 +0000</pubDate>
    <item>
      <title>Swift: краткий практический справочник</title>
      <link>https://write.rpod.club/devops/swift-kratkii-prakticheskii-spravochnik</link>
      <description>&lt;![CDATA[Краткое введение в повседневный Swift: объявления, типы, функции, коллекции, обработка ошибок, проектирование типов и конкурентность. &#xA;&#xA;Основано на темах&#xA;&#xA;The Swift Programming Language, Swift 6.4; примеры ниже написаны специально для этого конспекта.&#xA;&#xA;Swift — компилируемый статически типизированный язык. Компилятор выводит типы из контекста, но проверяет их до запуска программы. Язык старается сделать обычный код безопасным: значения инициализируются до использования, выход за границы коллекции проверяется, отсутствие значения выражается явно через Optional, а ошибки можно обрабатывать типизированно.&#xA;&#xA;1. Переменные, константы и типы&#xA;&#xA;Используйте let, если значение не должно меняться; var — если оно будет переприсваиваться. Тип часто можно не указывать: Swift выведет его из начального значения. Тип можно записать явно после двоеточия.&#xA;&#xA;let title = &#34;Swift&#34;           // String&#xA;var attempts: Int = 0&#xA;attempts += 1&#xA;&#xA;let price = 19.95             // Double&#xA;let isAvailable = true        // Bool&#xA;let message = &#34;\(title): \(attempts) attempt(s)&#34;&#xA;&#xA;String — строка, &#xA;Int — целое число, &#xA;Double — число с плавающей точкой,&#xA;Bool — логическое значение. &#xA;&#xA;Swift не выполняет неявное преобразование между разными числовыми типами:&#xA;&#xA;let whole = 4&#xA;let fraction = 0.5&#xA;let total = Double(whole) + fraction&#xA;&#xA;Условие в if должно иметь тип Bool; значения вроде 1 и 0 не считаются логическими.&#xA;&#xA;2. Условия и циклы&#xA;&#xA;let score = 82&#xA;&#xA;if score   = 90 {&#xA;    print(&#34;отлично&#34;)&#xA;} else if score   = 60 {&#xA;    print(&#34;зачёт&#34;)&#xA;} else {&#xA;    print(&#34;нужно повторить&#34;)&#xA;}&#xA;&#xA;for number in 1...3 {&#xA;    print(number)             // 1, 2, 3&#xA;}&#xA;&#xA;for index in 0..&lt;3 {&#xA;    print(index)              // 0, 1, 2&#xA;}&#xA;&#xA;... включает обе границы, ..&lt; не включает верхнюю. switch проверяет значение по нескольким шаблонам; каждый непустой вариант должен быть покрыт, обычно через default.&#xA;&#xA;let statusCode = 404&#xA;&#xA;switch statusCode {&#xA;case 200:&#xA;    print(&#34;успешно&#34;)&#xA;case 400..&lt;500:&#xA;    print(&#34;ошибка запроса&#34;)&#xA;case 500..&lt;600:&#xA;    print(&#34;ошибка сервера&#34;)&#xA;default:&#xA;    print(&#34;другой ответ&#34;)&#xA;}&#xA;&#xA;Для раннего выхода из функции или цикла используйте guard:&#xA;&#xA;func greeting(for name: String?) -  String {&#xA;    guard let name, !name.isEmpty else {&#xA;        return &#34;Привет!&#34;&#xA;    }&#xA;    return &#34;Привет, \(name)!&#34;&#xA;}&#xA;&#xA;Как работает guard:&#xA;&#xA;Условие в guard должно быть истинным, чтобы выполнение продолжилось.&#xA;Если условие ложно, выполняется ветка else, и из текущего scope нужен обязательный выход: return, throw, break, continue или fatalError.&#xA;Значения, извлечённые в guard let, доступны после guard в оставшейся части функции, что уменьшает вложенность по сравнению с if let.&#xA;&#xA;enum GuardExampleError: Error {&#xA;    case emptyMessage&#xA;    case missingRecipient&#xA;}&#xA;&#xA;func send(message: String?, to userID: String?) throws {&#xA;    guard let message, !message.isEmpty else {&#xA;        throw GuardExampleError.emptyMessage&#xA;    }&#xA;    guard let userID else {&#xA;        throw GuardExampleError.missingRecipient&#xA;    }&#xA;&#xA;    // message и userID здесь уже не Optional&#xA;    print(&#34;Send &#39;\(message)&#39; to \(userID)&#34;)&#xA;}&#xA;&#xA;3. Функции и замыкания&#xA;&#xA;Функция объявляется через func. Внешние метки аргументов делают вызов читаемым; ` убирает метку. Возвращаемый тип указывается после -  `.&#xA;&#xA;func convert( value: Double, toUnit unit: String = &#34;km&#34;) -  String {&#xA;    &#34;\(value) \(unit)&#34;&#xA;}&#xA;&#xA;let distance = convert(12.5, toUnit: &#34;mi&#34;)&#xA;let defaultDistance = convert(12.5)&#xA;&#xA;Замыкание — блок кода, который можно сохранить или передать функции. Методы коллекций map, filter и sorted принимают замыкания:&#xA;&#xA;let values = [3, 1, 4, 2]&#xA;let squares = values.map { $0  $0 }&#xA;let evenSquares = squares.filter { $0.isMultiple(of: 2) }&#xA;let ascending = values.sorted { left, right in left &lt; right }&#xA;&#xA;$0, $1 и далее — краткие имена параметров замыкания. Если логика сложная, дайте параметрам имена или вынесите код в отдельную функцию.&#xA;&#xA;4. Строки и коллекции&#xA;&#xA;Array хранит упорядоченный список, Set — уникальные значения без фиксированного порядка, Dictionary связывает ключи со значениями.&#xA;&#xA;var tasks = [&#34;прочитать&#34;, &#34;повторить&#34;]&#xA;tasks.append(&#34;попрактиковаться&#34;)&#xA;&#xA;let uniqueTags: SetString = [&#34;swift&#34;, &#34;cli&#34;, &#34;swift&#34;]&#xA;let scores = [&#34;Ada&#34;: 98, &#34;Linus&#34;: 91]&#xA;&#xA;if let adaScore = scores[&#34;Ada&#34;] {&#xA;    print(&#34;Оценка Ada: \(adaScore)&#34;)&#xA;}&#xA;&#xA;for task in tasks {&#xA;    print(task)&#xA;}&#xA;&#xA;Индекс массива допустим только в диапазоне 0..&lt;count; доступ по ключу словаря возвращает optional, поскольку ключа может не быть. Не обращайтесь к индексу, если коллекция может быть пустой.&#xA;&#xA;Строки объединяют интерполяцией (&#34;\(value)&#34;). Многострочные литералы заключаются в &#34;&#34;&#34;; граница отступа задаётся закрывающими кавычками.&#xA;&#xA;let name = &#34;Mina&#34;&#xA;let note = &#34;&#34;&#34;&#xA;    Участник: \(name)&#xA;    Статус: активен&#xA;    &#34;&#34;&#34;&#xA;&#xA;5. Optional и безопасная работа с отсутствием значения&#xA;&#xA;Int? — это OptionalInt: значение либо содержит Int, либо равно nil.&#xA;Optional заставляет явно обработать ситуацию, когда данных нет.&#xA;&#xA;let rawAge = &#34;34&#34;&#xA;let age = Int(rawAge)                 // Int?&#xA;&#xA;if let age {&#xA;    print(&#34;Через год: \(age + 1)&#34;)&#xA;} else {&#xA;    print(&#34;Возраст не распознан&#34;)&#xA;}&#xA;&#xA;if let временно разворачивает optional только в успешной ветке. Для значения по умолчанию используйте ??; для последовательного доступа — optional chaining ?..&#xA;&#xA;let nickname: String? = nil&#xA;let displayName = nickname ?? &#34;Гость&#34;&#xA;&#xA;struct Profile {&#xA;    let email: String?&#xA;}&#xA;&#xA;let profile: Profile? = Profile(email: &#34;user@example.org&#34;)&#xA;let email = profile?.email            // String?&#xA;&#xA;Не используйте ! для обычного разворачивания: если значения нет, программа аварийно завершится. Принудительное разворачивание уместно только там, где инвариант гарантирован и проверен.&#xA;&#xA;6. Ошибки&#xA;&#xA;Для ожидаемых сбоев функция может объявлять throws. Вызов такой функции помечается try, а ошибка обрабатывается через do/catch.&#xA;&#xA;enum InputError: Error {&#xA;    case empty&#xA;    case notANumber(String)&#xA;}&#xA;&#xA;func parseScore( text: String) throws -  Int {&#xA;    guard !text.isEmpty else { throw InputError.empty }&#xA;    guard let score = Int(text) else {&#xA;        throw InputError.notANumber(text)&#xA;    }&#xA;    return score&#xA;}&#xA;&#xA;do {&#xA;    let score = try parseScore(&#34;87&#34;)&#xA;    print(&#34;Баллы: \(score)&#34;)&#xA;} catch InputError.empty {&#xA;    print(&#34;Введите значение&#34;)&#xA;} catch {&#xA;    print(&#34;Не удалось разобрать ввод: \(error)&#34;)&#xA;}&#xA;&#xA;Проброс ошибки через несколько функций&#xA;&#xA;Если функция не может (или не должна) решать, как обрабатывать ошибку, она просто помечается throws и пробрасывает ошибку выше по цепочке вызовов.&#xA;&#xA;enum MessagingError: Error {&#xA;    case missingRecipient&#xA;    case transportOffline&#xA;}&#xA;&#xA;func validateRecipient( id: String?) throws -  String {&#xA;    guard let id, !id.isEmpty else {&#xA;        throw MessagingError.missingRecipient&#xA;    }&#xA;    return id&#xA;}&#xA;&#xA;func buildPayload(text: String, recipientID: String?) throws -  [String: String] {&#xA;    let recipient = try validateRecipient(recipientID)  // ошибка идёт дальше&#xA;    return [&#34;to&#34;: recipient, &#34;text&#34;: text]&#xA;}&#xA;&#xA;func performRequest(payload: [String: String]) throws {&#xA;    let online = false&#xA;    guard online else { throw MessagingError.transportOffline }&#xA;    print(&#34;Send payload: \(payload)&#34;)&#xA;}&#xA;&#xA;func sendMessage(text: String, recipientID: String?) throws {&#xA;    let payload = try buildPayload(text: text, recipientID: recipientID)&#xA;    try performRequest(payload: payload)                // проброс на уровень выше&#xA;}&#xA;&#xA;do {&#xA;    try sendMessage(text: &#34;Hi&#34;, recipientID: nil)&#xA;} catch MessagingError.missingRecipient {&#xA;    print(&#34;Укажите получателя&#34;)&#xA;} catch MessagingError.transportOffline {&#xA;    print(&#34;Сеть недоступна, повторите позже&#34;)&#xA;} catch {&#xA;    print(&#34;Неожиданная ошибка: \(error)&#34;)&#xA;}&#xA;&#xA;try? превращает ошибку в nil, если достаточно знать только «получилось/не получилось». try! отключает обработку ошибки и подходит лишь для гарантированно безопасного вызова.&#xA;&#xA;7. Свои типы: struct, enum, class&#xA;&#xA;struct — тип-значение: при присваивании или передаче копируется само значение. Это хороший выбор по умолчанию для моделей данных. class — ссылочный тип: несколько переменных могут указывать на один экземпляр;&#xA;классы поддерживают наследование.&#xA;&#xA;struct Book {&#xA;    let title: String&#xA;    var page: Int&#xA;&#xA;    mutating func turnPage() {&#xA;        page += 1&#xA;    }&#xA;}&#xA;&#xA;var book = Book(title: &#34;Практика Swift&#34;, page: 1)&#xA;book.turnPage()&#xA;&#xA;mutating разрешает методу структуры изменять её свойства. let-экземпляр структуры неизменяем целиком.&#xA;&#xA;Перечисление задаёт конечный набор вариантов. Варианты могут содержать значения; switch позволяет обработать их по типобезопасным шаблонам.&#xA;&#xA;import Foundation&#xA;&#xA;enum DownloadState {&#xA;    case idle&#xA;    case loading(progress: Double)&#xA;    case finished(URL)&#xA;    case failed(message: String)&#xA;}&#xA;&#xA;func describe( state: DownloadState) -  String {&#xA;    return switch state {&#xA;    case .idle:&#xA;        &#34;ожидание&#34;&#xA;    case .loading(let progress):&#xA;        &#34;загрузка: \(Int(progress  100))%&#34;&#xA;    case .finished(let url):&#xA;        &#34;готово: \(url.lastPathComponent)&#34;&#xA;    case .failed(let message):&#xA;        &#34;ошибка: \(message)&#34;&#xA;    }&#xA;}&#xA;&#xA;Здесь URL требует import Foundation.&#xA;&#xA;8. Протоколы, расширения и обобщения&#xA;&#xA;Протокол описывает требования к типу. Тип принимает эти требования через conformance; общий код может работать с любым подходящим типом.&#xA;&#xA;protocol IdentifiableItem {&#xA;    var id: String { get }&#xA;    var displayName: String { get }&#xA;}&#xA;&#xA;struct Note: IdentifiableItem {&#xA;    let id: String&#xA;    let displayName: String&#xA;}&#xA;&#xA;func printItemT: IdentifiableItem( item: T) {&#xA;    print(&#34;\(item.displayName) [\(item.id)]&#34;)&#xA;}&#xA;&#xA;T — параметр типа. Ограничение T: IdentifiableItem гарантирует, что у значения есть нужные свойства. Так сохраняется конкретный тип без приведения к небезопасному Any.&#xA;&#xA;Расширение добавляет вычисляемые свойства и методы существующему типу, не изменяя его исходное объявление:&#xA;&#xA;import Foundation&#xA;&#xA;extension String {&#xA;    var isBlank: Bool {&#xA;        trimmingCharacters(in: .whitespacesAndNewlines).isEmpty&#xA;    }&#xA;}&#xA;&#xA;Этот пример также требует import Foundation.&#xA;&#xA;9. Ссылки и управление памятью&#xA;&#xA;Классы управляются ARC (Automatic Reference Counting): объект освобождается, когда на него больше нет сильных ссылок. Сильные циклы ссылок могут удерживать объекты в памяти. Для обратных ссылок применяют weak или unowned с учётом их разных гарантий (weak становится nil, поэтому объявляется optional).&#xA;&#xA;final class Controller {&#xA;    var onClose: (() -  Void)?&#xA;&#xA;    func configure() {&#xA;        onClose = { [weak self] in&#xA;            self?.close()&#xA;        }&#xA;    }&#xA;&#xA;    private func close() {&#xA;        print(&#34;Закрыто&#34;)&#xA;    }&#xA;}&#xA;&#xA;Захват [weak self] не даёт замыканию удерживать Controller сильной ссылкой. Для структур и перечислений проблема циклического удержания не возникает так же, как для ссылочных типов.&#xA;&#xA;10. Асинхронность и конкурентность&#xA;&#xA;async помечает асинхронную функцию, await — точку, где выполнение может приостановиться, пока функция получает результат. Асинхронный вызов обычно не блокирует поток.&#xA;&#xA;func loadTitle() async throws -  String {&#xA;    try await Task.sleep(for: .milliseconds(20))&#xA;    return &#34;Готово&#34;&#xA;}&#xA;&#xA;func showTitle() async {&#xA;    do {&#xA;        print(try await loadTitle())&#xA;    } catch {&#xA;        print(&#34;Задача отменена или завершилась с ошибкой&#34;)&#xA;    }&#xA;}&#xA;&#xA;Несколько независимых задач можно запустить параллельно через async let;&#xA;результат читается с await. &#xA;Для динамического количества дочерних задач используют task group. Структурированная конкурентность связывает время жизни дочерних задач с родительской задачей.&#xA;&#xA;actor защищает своё изменяемое состояние: доступ к его изолированным свойствам из другого контекста требует await.&#xA;&#xA;actor Counter {&#xA;    private var value = 0&#xA;&#xA;    func increment() {&#xA;        value += 1&#xA;    }&#xA;&#xA;    func currentValue() -  Int {&#xA;        value&#xA;    }&#xA;}&#xA;&#xA;func readCounter() async {&#xA;    let counter = Counter()&#xA;    await counter.increment()&#xA;    let value = await counter.currentValue()&#xA;    print(value)&#xA;}&#xA;&#xA;В Swift 6 компилятор строже проверяет передачу данных между конкурентными контекстами. Тип Sendable означает, что значение безопасно передавать между задачами; value-типы с безопасными полями обычно проще сделать потокобезопасными, чем разделяемое изменяемое состояние.&#xA;&#xA;Ниже — самые полезные практические паттерны async-кода.&#xA;&#xA;10.1 Параллельные независимые запросы (async let)&#xA;&#xA;Если операции не зависят друг от друга, запускайте их одновременно:&#xA;&#xA;func loadProfileName() async throws -  String {&#xA;    try await Task.sleep(for: .milliseconds(30))&#xA;    return &#34;Alex&#34;&#xA;}&#xA;&#xA;func loadUnreadCount() async throws -  Int {&#xA;    try await Task.sleep(for: .milliseconds(40))&#xA;    return 7&#xA;}&#xA;&#xA;func loadDashboard() async throws -  String {&#xA;    async let name = loadProfileName()&#xA;    async let unread = loadUnreadCount()&#xA;&#xA;    // await здесь дождётся обоих результатов&#xA;    return &#34;User: \(try await name), unread: \(try await unread)&#34;&#xA;}&#xA;&#xA;async let хорошо подходит для фиксированного числа задач. Если задач динамически много — используйте TaskGroup.&#xA;&#xA;10.2 Динамический fan-out (withThrowingTaskGroup)&#xA;&#xA;TaskGroup позволяет создать переменное число дочерних задач и собрать их результаты по мере готовности.&#xA;&#xA;func fetchValue(id: Int) async throws -  Int {&#xA;    try await Task.sleep(for: .milliseconds(10))&#xA;    return id  id&#xA;}&#xA;&#xA;func loadAll(ids: [Int]) async throws -  [Int] {&#xA;    try await withThrowingTaskGroup(of: Int.self, returning: [Int].self) { group in&#xA;        for id in ids {&#xA;            group.addTask {&#xA;                try await fetchValue(id: id)&#xA;            }&#xA;        }&#xA;&#xA;        var result: [Int] = []&#xA;        for try await value in group {&#xA;            result.append(value)&#xA;        }&#xA;        return result.sorted()&#xA;    }&#xA;}&#xA;&#xA;Если одна дочерняя задача бросит ошибку, withThrowingTaskGroup пробросит её наружу, а остальные дочерние задачи будут отменены.&#xA;&#xA;10.3 Отмена задач (cancellation)&#xA;&#xA;Отмена кооперативная: задача должна сама проверять флаг отмены и корректно завершаться.&#xA;&#xA;func longOperation() async throws -  Int {&#xA;    var sum = 0&#xA;    for i in 1...10000 {&#xA;        if i.isMultiple(of: 500) {&#xA;            try Task.checkCancellation()   // бросает CancellationError&#xA;            try await Task.sleep(for: .milliseconds(1))&#xA;        }&#xA;        sum += i&#xA;    }&#xA;    return sum&#xA;}&#xA;&#xA;let task = Task {&#xA;    try await longOperation()&#xA;}&#xA;&#xA;task.cancel()&#xA;&#xA;defer и withTaskCancellationHandler полезны для очистки ресурсов при досрочном завершении.&#xA;&#xA;10.4 UI-обновления и @MainActor&#xA;&#xA;Код, который трогает состояние UI-модели, лучше изолировать на главном акторе.&#xA;&#xA;@MainActor&#xA;final class ViewModel {&#xA;    private(set) var title = &#34;Loading...&#34;&#xA;&#xA;    func refresh() async {&#xA;        do {&#xA;            let text = try await loadTitle()&#xA;            title = text&#xA;        } catch {&#xA;            title = &#34;Error&#34;&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;@MainActor гарантирует последовательный доступ к этому состоянию из конкурентного кода и упрощает reasoning о гонках.&#xA;&#xA;10.5 Потоки данных (AsyncSequence)&#xA;&#xA;for await позволяет читать значения постепенно, как поток событий.&#xA;&#xA;struct CounterStream: AsyncSequence {&#xA;    typealias Element = Int&#xA;&#xA;    struct Iterator: AsyncIteratorProtocol {&#xA;        var current = 1&#xA;        mutating func next() async -  Int? {&#xA;            guard current &lt;= 3 else { return nil }&#xA;            defer { current += 1 }&#xA;            try? await Task.sleep(for: .milliseconds(20))&#xA;            return current&#xA;        }&#xA;    }&#xA;&#xA;    func makeAsyncIterator() -  Iterator { Iterator() }&#xA;}&#xA;&#xA;func consumeStream() async {&#xA;    for await value in CounterStream() {&#xA;        print(&#34;event:&#34;, value)&#xA;    }&#xA;}&#xA;&#xA;Такой стиль удобен для логов, сокетов, прогресса загрузки и любых&#xA;долгоживущих последовательностей событий.&#xA;&#xA;11. Инициализация и деинициализация&#xA;&#xA;Инициализатор init подготавливает тип к использованию. Для структур это часто главный способ гарантировать инварианты объекта. У классов дополнительно есть deinit, который вызывается перед освобождением экземпляра ARC.&#xA;&#xA;import Foundation&#xA;&#xA;struct Temperature {&#xA;    let celsius: Double&#xA;&#xA;    init(celsius: Double) {&#xA;        self.celsius = celsius&#xA;    }&#xA;&#xA;    init(fahrenheit: Double) {&#xA;        self.celsius = (fahrenheit - 32)  5 / 9&#xA;    }&#xA;}&#xA;&#xA;final class FileLogger {&#xA;    private let handle: FileHandle&#xA;&#xA;    init?(path: String) {&#xA;        guard FileManager.default.createFile(atPath: path, contents: nil) else {&#xA;            return nil&#xA;        }&#xA;        guard let h = FileHandle(forWritingAtPath: path) else { return nil }&#xA;        self.handle = h&#xA;    }&#xA;&#xA;    func write( line: String) throws {&#xA;        try handle.write(contentsOf: Data((line + &#34;\n&#34;).utf8))&#xA;    }&#xA;&#xA;    deinit {&#xA;        try? handle.close()&#xA;    }&#xA;}&#xA;&#xA;init? — проваливающийся инициализатор (может вернуть nil), полезен для валидации входных данных.&#xA;&#xA;12. Type Casting (is, as?, as!)&#xA;&#xA;Когда работа идёт с иерархией классов или массивом значений базового типа, часто нужно узнать реальный тип объекта во время выполнения.&#xA;&#xA;class MediaItem {&#xA;    let title: String&#xA;    init(title: String) { self.title = title }&#xA;}&#xA;&#xA;final class Movie: MediaItem {&#xA;    let director: String&#xA;    init(title: String, director: String) {&#xA;        self.director = director&#xA;        super.init(title: title)&#xA;    }&#xA;}&#xA;&#xA;final class Song: MediaItem {&#xA;    let artist: String&#xA;    init(title: String, artist: String) {&#xA;        self.artist = artist&#xA;        super.init(title: title)&#xA;    }&#xA;}&#xA;&#xA;let library: [MediaItem] = [&#xA;    Movie(title: &#34;Blade Runner&#34;, director: &#34;Ridley Scott&#34;),&#xA;    Song(title: &#34;Nude&#34;, artist: &#34;Radiohead&#34;)&#xA;]&#xA;&#xA;for item in library {&#xA;    if let movie = item as? Movie {&#xA;        print(&#34;Фильм: \(movie.title), режиссёр \(movie.director)&#34;)&#xA;    } else if let song = item as? Song {&#xA;        print(&#34;Трек: \(song.title), артист \(song.artist)&#34;)&#xA;    }&#xA;}&#xA;&#xA;is — проверка типа (item is Movie).&#xA;as? — безопасное приведение с Optional.&#xA;as! — принудительное приведение; используйте только при гарантии типа.&#xA;&#xA;13. Вложенные типы (Nested Types)&#xA;&#xA;Типы можно объявлять внутри других типов, чтобы локализовать детали модели.&#xA;&#xA;struct Payment {&#xA;    enum Method {&#xA;        case cash&#xA;        case card(last4: String)&#xA;    }&#xA;&#xA;    struct Receipt {&#xA;        let id: String&#xA;        let total: Double&#xA;    }&#xA;&#xA;    let method: Method&#xA;    let amount: Double&#xA;    let receipts: [Receipt]&#xA;}&#xA;&#xA;let p = Payment(&#xA;    method: .card(last4: &#34;1024&#34;),&#xA;    amount: 49.9,&#xA;    receipts: [&#xA;        .init(id: &#34;r-001&#34;, total: 29.9),&#xA;        .init(id: &#34;r-002&#34;, total: 20.0)&#xA;    ]&#xA;)&#xA;&#xA;// Снаружи nested-тип пишется через полное имя:&#xA;let allReceipts: [Payment.Receipt] = p.receipts&#xA;&#xA;Так внешнее API остаётся компактным, а связанные сущности не «засоряют» глобальное пространство имён.&#xA;&#xA;14. Opaque и Boxed protocol types (some и any)&#xA;&#xA;some Protocol скрывает конкретный возвращаемый тип от вызывающего кода, но оставляет его фиксированным для конкретной функции. any Protocol хранит значение неизвестного конкретного типа (type erasure, «коробка»).&#xA;&#xA;protocol Shape {&#xA;    func area() -  Double&#xA;}&#xA;&#xA;struct Square: Shape {&#xA;    let side: Double&#xA;    func area() -  Double { side  side }&#xA;}&#xA;&#xA;func makeUnitShape() -  some Shape {&#xA;    Square(side: 1)&#xA;}&#xA;&#xA;func printArea(of shape: any Shape) {&#xA;    print(shape.area())&#xA;}&#xA;&#xA;let unit = makeUnitShape()      // конкретный тип скрыт&#xA;printArea(of: unit)             // принимаем &#34;любую фигуру&#34;&#xA;&#xA;some полезен для стабильного API с сокрытием реализации, any — когда нужно хранить разнородные значения по протоколу в одной коллекции.&#xA;&#xA;15. Memory Safety и эксклюзивный доступ&#xA;&#xA;Swift запрещает одновременный конфликтующий доступ к памяти: например, две записи в одно место или чтение+запись, перекрывающиеся по времени.&#xA;&#xA;var balance = 100&#xA;&#xA;func deposit( amount: Int, to value: inout Int) {&#xA;    value += amount&#xA;}&#xA;&#xA;deposit(20, to: &amp;balance)       // OK&#xA;&#xA;Типичный анти-паттерн — одновременно передавать одну переменную как inout в два параметра одной функции (или читать и писать её же внутри пересекающихся областей). Компилятор и рантайм отслеживают такие конфликты и останавливают опасный код.&#xA;&#xA;16. Продвинутые операторы (фрагмент)&#xA;&#xA;Swift поддерживает побитовые операции и пользовательские операторы.&#xA;&#xA;let mask: UInt8 = 0b00001111&#xA;let value: UInt8 = 0b10101100&#xA;let lowBits = value &amp; mask              // 0b00001100&#xA;let shifted = value     2                // 0b00101011&#xA;&#xA;Пользовательский оператор стоит вводить только когда это реально улучшает читаемость доменной модели:&#xA;&#xA;infix operator : MultiplicationPrecedence&#xA;&#xA;func  (lhs: Double, rhs: Double) -  Double {&#xA;    pow(lhs, rhs)&#xA;}&#xA;&#xA;let x = 2.0  3.0                      // 8.0&#xA;&#xA;17. Макросы (обзор)&#xA;&#xA;Макросы генерируют Swift-код на этапе компиляции и уменьшают шаблонный код. В прикладном коде чаще встречается потребление уже готовых макросов, чем написание своих.&#xA;&#xA;@attached(member, names: named(init))&#xA;macro AutoInit()&#xA;&#xA;Важная мысль: макрос меняет исходный код до проверки типов, поэтому к диагностике и стилю надо относиться так же строго, как к обычному коду.&#xA;Для реального проекта макрос обычно подключается из отдельного пакета.&#xA;&#xA;18. Language Reference: короткий ориентир&#xA;&#xA;Полный справочник языка значительно формальнее Language Guide. Ниже минимум, который полезно знать в повседневной работе.&#xA;&#xA;18.1 Lexical Structure&#xA;&#xA;Лексика определяет, как исходник разбивается на токены: идентификаторы, литералы, операторы, комментарии, пробелы.&#xA;&#xA;let café = &#34;unicode identifier&#34;&#xA;let hex = 0xFF&#xA;let scientific = 1.2e3&#xA;&#xA;18.2 Types / Expressions / Statements&#xA;&#xA;Types: именованные (Int, String) и составные ((Int, String),&#xA;  () -  Void, [Int], [String: Int], Int?).&#xA;Expressions: вычисление значения (a + b, foo(), x ? y : z).&#xA;Statements: управление потоком (if, for, while, switch, guard,&#xA;  return, throw, defer).&#xA;&#xA;let pair: (Int, String) = (1, &#34;one&#34;)&#xA;let numbers: [Int] = [1, 2, 3]&#xA;&#xA;func execute( body: () -  Void) {&#xA;    defer { print(&#34;done&#34;) }&#xA;    body()&#xA;}&#xA;&#xA;18.3 Declarations / Attributes / Patterns&#xA;&#xA;Declarations вводят имена: let, var, func, struct, class,&#xA;  enum, protocol, extension, typealias.&#xA;Attributes уточняют поведение декларации или типа:&#xA;  @discardableResult, @available, @MainActor, @Sendable.&#xA;Patterns используются в switch, if case, guard case, destructuring.&#xA;&#xA;@discardableResult&#xA;func log( message: String) -  Int {&#xA;    print(message)&#xA;    return message.count&#xA;}&#xA;&#xA;let point = (x: 2, y: 5)&#xA;switch point {&#xA;case (0, 0):&#xA;    print(&#34;origin&#34;)&#xA;case let (x, y) where x == y:&#xA;    print(&#34;diagonal \(x)&#34;)&#xA;default:&#xA;    print(&#34;(\(point.x), \(point.y))&#34;)&#xA;}&#xA;&#xA;18.4 Generic Parameters and Arguments&#xA;&#xA;Обобщения объявляют параметры типа (T) и ограничения (where), а при вызове компилятор подставляет конкретные аргументы типа.&#xA;&#xA;func firstMatchT(in values: [T], where predicate: (T) -  Bool) -  T? {&#xA;    for v in values where predicate(v) {&#xA;        return v&#xA;    }&#xA;    return nil&#xA;}&#xA;&#xA;let match = firstMatch(in: [10, 15, 20]) { $0.isMultiple(of: 3) } // 15&#xA;&#xA;18.5 Summary of Grammar&#xA;&#xA;Грамматика в книге — формальная EBNF-нотация для полного синтаксиса Swift.&#xA;&#xA;Практически она нужна, когда вы:&#xA;&#xA;разбираете сложный синтаксис из diagnostics компилятора;&#xA;пишете инструменты (lint/format/parser);&#xA;уточняете граничный случай языка.&#xA;&#xA;19. Пакеты, зависимости, форматирование и Makefile&#xA;&#xA;Даже в небольшом CLI-проекте лучше сразу держать единый workflow: зависимости через SwiftPM, одинаковое форматирование и повторяемые команды сборки.&#xA;&#xA;19.1 Как добавлять dependencies через SwiftPM&#xA;&#xA;В Package.swift зависимость объявляется в секции dependencies, а затем подключается к нужному target в targets[].dependencies.&#xA;&#xA;// swift-tools-version: 6.4&#xA;import PackageDescription&#xA;&#xA;let package = Package(&#xA;    name: &#34;demo-cli&#34;,&#xA;    platforms: [.macOS(.v13)],&#xA;    products: [&#xA;        .executable(name: &#34;demo-cli&#34;, targets: [&#34;demo-cli&#34;])&#xA;    ],&#xA;    dependencies: [&#xA;        .package(url: &#34;https://github.com/apple/swift-argument-parser&#34;, from: &#34;1.8.2&#34;)&#xA;    ],&#xA;    targets: [&#xA;        .executableTarget(&#xA;            name: &#34;demo-cli&#34;,&#xA;            dependencies: [&#xA;                .product(name: &#34;ArgumentParser&#34;, package: &#34;swift-argument-parser&#34;)&#xA;            ]&#xA;        )&#xA;    ]&#xA;)&#xA;&#xA;Базовые команды:&#xA;&#xA;swift package resolve        # резолв зависимостей и запись Package.resolved&#xA;swift build                  # сборка&#xA;swift test                   # тесты&#xA;swift package update         # обновить версии в допустимых границах&#xA;&#xA;Практика:&#xA;фиксируйте минимально нужную версию (from:), не «слишком новую заранее»;&#xA;коммитьте Package.resolved для воспроизводимых сборок;&#xA;после добавления dependency сразу проверяйте swift build и swift test.&#xA;&#xA;19.2 Форматирование и линтинг (swift-format)&#xA;&#xA;Обычно используют swift-format c конфигом в корне проекта.&#xA;&#xA;Пример .swift-format:&#xA;&#xA;{&#xA;  &#34;version&#34;: 1,&#xA;  &#34;indentation&#34;: { &#34;spaces&#34;: 4 },&#xA;  &#34;lineLength&#34;: 120&#xA;}&#xA;&#xA;Полезные команды:&#xA;&#xA;swift format lint --strict --recursive Sources Tests&#xA;swift format format --in-place --recursive Sources Tests&#xA;&#xA;lint проверяет стиль без изменения файлов;&#xA;format автоматически переписывает код по правилам.&#xA;&#xA;19.3 Пример Makefile для повседневной работы&#xA;&#xA;Makefile убирает «магические» команды из головы и стандартизирует CI/локалку.&#xA;&#xA;.PHONY: build test lint fmt check release clean docs&#xA;&#xA;build:&#xA;&#x9;swift build&#xA;&#xA;test:&#xA;&#x9;swift test&#xA;&#xA;lint:&#xA;&#x9;swift format lint --strict --recursive Sources Tests&#xA;&#xA;fmt:&#xA;&#x9;swift format format --in-place --recursive Sources Tests&#xA;&#xA;check: build test lint&#xA;&#xA;release:&#xA;&#x9;swift build -c release&#xA;&#xA;docs:&#xA;&#x9;swift package --allow-writing-to-package-directory generate-docc-reference&#xA;&#xA;clean:&#xA;&#x9;swift package clean&#xA;&#xA;Минимальный цикл перед коммитом:&#xA;&#xA;make check&#xA;&#xA;Для релиза:&#xA;&#xA;make release&#xA;&#xA;Если проект использует дополнительные инструменты (например, swiftlint), их добавляют отдельными целями (lint-swiftlint, lint-all) без смешивания с базовым swift format lint.&#xA;&#xA;20. Как правильно организовать структуру проекта&#xA;&#xA;Хорошая структура проекта решает три задачи: понятная навигация, независимое тестирование и безопасное расширение без «комка» зависимостей.&#xA;&#xA;20.1 Базовая структура SwiftPM-проекта&#xA;&#xA;Для CLI и библиотек удобен стандарт SwiftPM:&#xA;&#xA;my-project/&#xA;├─ Package.swift&#xA;├─ Package.resolved&#xA;├─ README.md&#xA;├─ Sources/&#xA;│  ├─ AppCLI/                  # executable target (точка входа)&#xA;│  │  ├─ main.swift            # или @main type&#xA;│  │  ├─ Commands/&#xA;│  │  │  ├─ RootCommand.swift&#xA;│  │  │  ├─ DeckCommand.swift&#xA;│  │  │  └─ CardCommand.swift&#xA;│  │  └─ Support/&#xA;│  │     └─ Output.swift&#xA;│  └─ Core/                    # library target с бизнес-логикой&#xA;│     ├─ Domain/&#xA;│     │  ├─ Deck.swift&#xA;│     │  └─ Card.swift&#xA;│     ├─ Services/&#xA;│     │  ├─ SchedulerService.swift&#xA;│     │  └─ ReviewService.swift&#xA;│     └─ Persistence/&#xA;│        ├─ Database.swift&#xA;│        └─ Repositories/&#xA;├─ Tests/&#xA;│  ├─ CoreTests/&#xA;│  └─ AppCLITests/&#xA;├─ docs/&#xA;└─ Makefile&#xA;&#xA;Идея: AppCLI занимается только парсингом аргументов/выводом, а вся логика живёт в Core. Это упрощает тесты и повторное использование кода.&#xA;&#xA;20.2 Принцип «слои и зависимости в одну сторону»&#xA;&#xA;Хорошее правило для модулей:&#xA;&#xA;Domain не зависит от CLI/БД/сети;&#xA;Services зависят от Domain;&#xA;Persistence реализует протоколы (репозитории/хранилища);&#xA;AppCLI зависит от Core, но не наоборот.&#xA;&#xA;Такой граф зависимостей предотвращает циклы и делает замену инфраструктуры (например, SQLite → Postgres, CLI → HTTP API) локальной задачей.&#xA;&#xA;20.3 Группировка по feature vs по техническим слоям&#xA;&#xA;Есть два рабочих подхода:&#xA;&#xA;По слоям (Domain/Services/Persistence) — проще для небольших и средних&#xA;  проектов.&#xA;По фичам (Deck/Review/Import) — удобнее, когда команд и сценариев становится много.&#xA;&#xA;Компромисс: в Core держать feature-папки, а внутри каждой — мини-слои.&#xA;&#xA;Sources/Core/&#xA;├─ Deck/&#xA;│  ├─ Deck.swift&#xA;│  ├─ DeckService.swift&#xA;│  └─ DeckRepository.swift&#xA;└─ Review/&#xA;   ├─ Review.swift&#xA;   ├─ ReviewService.swift&#xA;   └─ ReviewRepository.swift&#xA;&#xA;20.4 Тестовая структура&#xA;&#xA;Tests/CoreTests — юнит-тесты чистой логики;&#xA;Tests/AppCLITests — проверка команд, аргументов и текстового вывода;&#xA;общие тестовые фикстуры: Tests/TestSupport (builders, mocks, sample data).&#xA;&#xA;Полезный принцип: тесты должны повторять структуру Sources, чтобы нужный тест находился по имени файла/фичи за несколько секунд.&#xA;&#xA;20.5 Именование файлов и типов&#xA;&#xA;Один основной тип на файл (DeckService.swift → DeckService);&#xA;суффиксы по роли: ...Service, ...Repository, ...Command;&#xA;протоколы рядом с use-case (DeckRepository + SQLiteDeckRepository);&#xA;не смешивайте в одном файле CLI-команды и предметную логику.&#xA;&#xA;20.6 Что часто ломает структуру&#xA;&#xA;«Утечка» Foundation/SQL/HTTP в domain-модели без необходимости;&#xA;создание god-file вроде Utils.swift или Helpers.swift с десятками&#xA;  несвязанных функций;&#xA;дублирование моделей отдельно для CLI и core без чёткой причины;&#xA;циклические импорты между target-ами.&#xA;&#xA;Если сомневаетесь, выносите не по «красоте», а по границам изменений: код, который изменяется вместе, должен лежать рядом.&#xA;&#xA;20.7 Как AppCLI видит Core и что делать с одинаковыми именами&#xA;&#xA;AppCLI «видит» Core, только если зависимость явно объявлена в&#xA;Package.swift, а в коде есть import Core.&#xA;&#xA;// Package.swift (фрагмент)&#xA;targets: [&#xA;    .target(name: &#34;Core&#34;),&#xA;    .executableTarget(&#xA;        name: &#34;AppCLI&#34;,&#xA;        dependencies: [&#34;Core&#34;]&#xA;    )&#xA;]&#xA;&#xA;// Sources/AppCLI/main.swift&#xA;import Core&#xA;&#xA;let service = DeckService()&#xA;&#xA;Важно: файлы Deck.swift и Card.swift внутри одного и того же target образуют один namespace модуля Core. Поэтому две глобальные функции с одним и тем же именем и сигнатурой вызовут ошибку компиляции, даже если лежат в разных файлах.&#xA;&#xA;Плохой пример (конфликт):&#xA;&#xA;// Deck.swift&#xA;func parse( text: String) -  Int { 1 }&#xA;&#xA;// Card.swift&#xA;func parse( text: String) -  Int { 2 } // redeclaration error&#xA;&#xA;Правильные варианты:&#xA;&#xA;держать функции как методы соответствующих типов;&#xA;использовать namespace через enum/struct без состояния;&#xA;различать сигнатуры (например, argument labels), если это действительно&#xA;  разные операции.&#xA;&#xA;enum DeckParser {&#xA;    static func parse( text: String) -  Int { 1 }&#xA;}&#xA;&#xA;enum CardParser {&#xA;    static func parse( text: String) -  Int { 2 }&#xA;}&#xA;&#xA;let a = DeckParser.parse(&#34;...&#34;)&#xA;let b = CardParser.parse(&#34;...&#34;)&#xA;&#xA;Если одинаковые имена приходят из разных модулей, обычно помогает явная квалификация через имя модуля/типа, чтобы снять неоднозначность.&#xA;&#xA;21. Подключение внешних lib.so&#xA;&#xA;На Linux в Swift есть два основных сценария:&#xA;&#xA;статическое подключение API на этапе сборки (рекомендуется): через&#xA;  systemLibrary target в SwiftPM;&#xA;динамическая загрузка в рантайме: через dlopen/dlsym.&#xA;&#xA;21.1 Рекомендуемый путь: systemLibrary + modulemap&#xA;&#xA;Если у вас есть libmylib.so и заголовок mylib.h, подключайте библиотеку как C-модуль.&#xA;&#xA;Package.swift (пример):&#xA;&#xA;// swift-tools-version: 6.4&#xA;import PackageDescription&#xA;&#xA;let package = Package(&#xA;    name: &#34;demo&#34;,&#xA;    targets: [&#xA;        .systemLibrary(&#xA;            name: &#34;CMyLib&#34;,&#xA;            pkgConfig: &#34;mylib&#34;&#xA;        ),&#xA;        .executableTarget(&#xA;            name: &#34;app&#34;,&#xA;            dependencies: [&#34;CMyLib&#34;],&#xA;            linkerSettings: [&#xA;                .unsafeFlags([&#34;-L/opt/mylib/lib&#34;]),&#xA;                .linkedLibrary(&#34;mylib&#34;)&#xA;            ]&#xA;        )&#xA;    ]&#xA;)&#xA;&#xA;Sources/CMyLib/module.modulemap:&#xA;&#xA;module CMyLib [system] {&#xA;  header &#34;/opt/mylib/include/mylib.h&#34;&#xA;  link &#34;mylib&#34;&#xA;  export &#xA;}&#xA;&#xA;Использование в Swift:&#xA;&#xA;import CMyLib&#xA;&#xA;let rc = mylibinit()&#xA;print(&#34;init rc =&#34;, rc)&#xA;&#xA;Что важно:&#xA;библиотека должна иметь C ABI;&#xA;если исходник на C++, обычно нужен C-wrapper (extern &#34;C&#34;);&#xA;рантайм должен находить .so (через LDLIBRARYPATH, rpath или установку в системный путь линковщика).&#xA;&#xA;21.2 Динамическая загрузка в рантайме: dlopen/dlsym&#xA;&#xA;Нужна, если библиотека опциональна или выбирается по конфигу во время запуска.&#xA;&#xA;import Glibc&#xA;&#xA;typealias InitFn = @convention(c) () -  Int32&#xA;&#xA;let handle = dlopen(&#34;/opt/mylib/lib/libmylib.so&#34;, RTLDNOW)&#xA;guard handle != nil else {&#xA;    fatalError(String(cString: dlerror()))&#xA;}&#xA;defer { dlclose(handle) }&#xA;&#xA;guard let sym = dlsym(handle, &#34;mylibinit&#34;) else {&#xA;    fatalError(String(cString: dlerror()))&#xA;}&#xA;&#xA;let fn = unsafeBitCast(sym, to: InitFn.self)&#xA;let rc = fn()&#xA;print(&#34;init rc =&#34;, rc)&#xA;&#xA;Минусы этого подхода: меньше проверок на этапе компиляции и выше риск ошибок в подписи функции/символах. Поэтому для обычного проекта предпочтителен systemLibrary.&#xA;&#xA;22. Один проект: lib.so + несколько утилит&#xA;&#xA;Да, в одном SwiftPM-проекте можно собирать сразу несколько артефактов:&#xA;динамическую библиотеку и несколько исполняемых файлов, которые от неё зависят.&#xA;&#xA;22.1 Пример Package.swift&#xA;&#xA;// swift-tools-version: 6.4&#xA;import PackageDescription&#xA;&#xA;let package = Package(&#xA;    name: &#34;my-stack&#34;,&#xA;    products: [&#xA;        .library(name: &#34;Core&#34;, type: .dynamic, targets: [&#34;Core&#34;]), // libCore.so&#xA;        .executable(name: &#34;server&#34;, targets: [&#34;Server&#34;]),&#xA;        .executable(name: &#34;client&#34;, targets: [&#34;Client&#34;])&#xA;    ],&#xA;    targets: [&#xA;        .target(name: &#34;Core&#34;),&#xA;        .executableTarget(name: &#34;Server&#34;, dependencies: [&#34;Core&#34;]),&#xA;        .executableTarget(name: &#34;Client&#34;, dependencies: [&#34;Core&#34;])&#xA;    ]&#xA;)&#xA;&#xA;Сборка:&#xA;&#xA;swift build -c release&#xA;swift build -c release --product server&#xA;swift build -c release --product client&#xA;&#xA;По умолчанию артефакты лежат в .build/..., а не в ./lib и ./bin.&#xA;&#xA;22.2 Как получить layout lib/ + bin/&#xA;&#xA;Обычно добавляют install-цель в Makefile, которая копирует артефакты после swift build -c release.&#xA;&#xA;.PHONY: release install&#xA;&#xA;release:&#xA;&#x9;swift build -c release&#xA;&#xA;install: release&#xA;&#x9;mkdir -p ./lib ./bin&#xA;&#x9;cp ./.build/release/libCore.so ./lib/&#xA;&#x9;cp ./.build/release/server ./bin/&#xA;&#x9;cp ./.build/release/client ./bin/&#xA;&#xA;Чтобы server/client нашли libCore.so при запуске, используйте один из вариантов:&#xA;&#xA;LDLIBRARYPATH=./lib ./bin/server&#xA;rpath при линковке (предпочтительно для поставки)&#xA;установка .so в системный путь линковщика&#xA;&#xA;22.3 Рекомендуемая структура для такого проекта&#xA;&#xA;my-stack/&#xA;├─ Package.swift&#xA;├─ Sources/&#xA;│  ├─ Core/&#xA;│  │  ├─ Domain/&#xA;│  │  ├─ Services/&#xA;│  │  └─ Persistence/&#xA;│  ├─ Server/&#xA;│  │  └─ main.swift&#xA;│  └─ Client/&#xA;│     └─ main.swift&#xA;├─ Tests/&#xA;│  ├─ CoreTests/&#xA;│  ├─ ServerTests/&#xA;│  └─ ClientTests/&#xA;├─ bin/                        # install-артефакты (не из git)&#xA;├─ lib/                        # install-артефакты (не из git)&#xA;└─ Makefile&#xA;&#xA;Core держит бизнес-логику и контракты, Server/Client — только transport, CLI/сеть и composition root. Это уменьшает дублирование и упрощает тестирование.&#xA;&#xA;23. Парсинг аргументов CLI: команды и подкоманды&#xA;&#xA;Для production CLI в Swift обычно используют&#xA;swift-argument-parser:&#xA;он даёт типобезопасные аргументы, автогенерацию --help и понятную структуру команд.&#xA;&#xA;23.1 Базовый шаблон команды&#xA;&#xA;import ArgumentParser&#xA;&#xA;@main&#xA;struct App: ParsableCommand {&#xA;    static let configuration = CommandConfiguration(&#xA;        commandName: &#34;app&#34;,&#xA;        abstract: &#34;Demo CLI&#34;&#xA;    )&#xA;&#xA;    @Argument(help: &#34;Имя пользователя&#34;)&#xA;    var name: String&#xA;&#xA;    @Option(name: .shortAndLong, help: &#34;Количество повторов&#34;)&#xA;    var count: Int = 1&#xA;&#xA;    @Flag(name: .shortAndLong, help: &#34;Подробный вывод&#34;)&#xA;    var verbose = false&#xA;&#xA;    mutating func run() throws {&#xA;        guard count   0 else {&#xA;            throw ValidationError(&#34;--count must be   0&#34;)&#xA;        }&#xA;        for  in 0..&lt;count {&#xA;            print(verbose ? &#34;Hello, \(name) [verbose]&#34; : &#34;Hello, \(name)&#34;)&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;Здесь:&#xA;@Argument — позиционный аргумент;&#xA;@Option — параметр со значением (--count 3);&#xA;@Flag — булев переключатель (--verbose).&#xA;&#xA;23.2 Иерархия команд и подкоманд&#xA;&#xA;Корневой тип описывает дерево подкоманд через CommandConfiguration.&#xA;&#xA;import ArgumentParser&#xA;&#xA;@main&#xA;struct Recall: ParsableCommand {&#xA;    static let configuration = CommandConfiguration(&#xA;        commandName: &#34;recall&#34;,&#xA;        abstract: &#34;Flashcards CLI&#34;,&#xA;        subcommands: [Deck.self, Card.self]&#xA;    )&#xA;}&#xA;&#xA;struct Deck: ParsableCommand {&#xA;    static let configuration = CommandConfiguration(&#xA;        abstract: &#34;Manage decks&#34;,&#xA;        subcommands: [DeckAdd.self, DeckList.self, DeckDelete.self]&#xA;    )&#xA;}&#xA;&#xA;struct DeckAdd: ParsableCommand {&#xA;    static let configuration = CommandConfiguration(&#xA;        commandName: &#34;add&#34;,&#xA;        abstract: &#34;Add deck&#34;&#xA;    )&#xA;&#xA;    @Argument(help: &#34;Deck name&#34;)&#xA;    var name: String&#xA;&#xA;    mutating func run() throws {&#xA;        print(&#34;add deck:&#34;, name)&#xA;    }&#xA;}&#xA;&#xA;Так пользователь получает предсказуемый UX:&#xA;recall deck add rust&#xA;recall deck list&#xA;recall card add ...&#xA;&#xA;23.3 Где делать валидацию аргументов&#xA;&#xA;Есть два основных места:&#xA;&#xA;validate() — проверка корректности параметров до run();&#xA;run() — выполнение команды и бизнес-логика.&#xA;&#xA;struct ImportCommand: ParsableCommand {&#xA;    @Option(help: &#34;Path to file&#34;)&#xA;    var file: String&#xA;&#xA;    func validate() throws {&#xA;        guard file.hasSuffix(&#34;.txt&#34;) else {&#xA;            throw ValidationError(&#34;Only .txt files are supported&#34;)&#xA;        }&#xA;    }&#xA;&#xA;    mutating func run() throws {&#xA;        print(&#34;Import from&#34;, file)&#xA;    }&#xA;}&#xA;&#xA;23.4 Рекомендуемая структура файлов для CLI-команд&#xA;&#xA;Sources/AppCLI/&#xA;├─ main.swift&#xA;├─ Commands/&#xA;│  ├─ RootCommand.swift&#xA;│  ├─ Deck/&#xA;│  │  ├─ DeckCommand.swift&#xA;│  │  ├─ DeckAddCommand.swift&#xA;│  │  ├─ DeckListCommand.swift&#xA;│  │  └─ DeckDeleteCommand.swift&#xA;│  └─ Card/&#xA;│     ├─ CardCommand.swift&#xA;│     └─ CardAddCommand.swift&#xA;└─ Support/&#xA;   ├─ Output.swift&#xA;   └─ Parsing/&#xA;      └─ CommonOptions.swift&#xA;&#xA;Правило: корневые команды/подкоманды живут в Commands, переиспользуемые типы аргументов и опций — в Support.&#xA;&#xA;24. Mock в тестах: как подменять зависимости&#xA;&#xA;В Swift чаще всего делают mock через протокол и подставную реализацию в тесте.&#xA;Это позволяет тестировать бизнес-логику без реальной БД, сети или файловой системы.&#xA;&#xA;24.1 Базовый паттерн: Protocol + Service + Mock&#xA;&#xA;protocol DeckRepository {&#xA;    func save(name: String) throws&#xA;}&#xA;&#xA;enum DeckError: Error {&#xA;    case emptyName&#xA;}&#xA;&#xA;struct DeckService {&#xA;    let repository: DeckRepository&#xA;&#xA;    func createDeck(name: String) throws {&#xA;        guard !name.isEmpty else { throw DeckError.emptyName }&#xA;        try repository.save(name: name)&#xA;    }&#xA;}&#xA;&#xA;Mock-реализация для теста:&#xA;&#xA;final class MockDeckRepository: DeckRepository {&#xA;    var savedNames: [String] = []&#xA;    var saveError: Error?&#xA;&#xA;    func save(name: String) throws {&#xA;        if let saveError { throw saveError }&#xA;        savedNames.append(name)&#xA;    }&#xA;}&#xA;&#xA;Пример теста:&#xA;&#xA;import Testing&#xA;@testable import Core&#xA;&#xA;@Test&#xA;func createDecksavesName() throws {&#xA;    let mock = MockDeckRepository()&#xA;    let service = DeckService(repository: mock)&#xA;&#xA;    try service.createDeck(name: &#34;swift&#34;)&#xA;&#xA;    #expect(mock.savedNames == [&#34;swift&#34;])&#xA;}&#xA;&#xA;24.2 Mock, Stub, Spy — в чём разница&#xA;&#xA;Stub: возвращает заранее подготовленный результат.&#xA;Mock: проверяет ожидаемые взаимодействия (вызов, аргументы, количество).&#xA;Spy: записывает факты вызовов, которые проверяются после выполнения.&#xA;&#xA;На практике одна тестовая реализация может совмещать роли (например, MockDeckRepository выше — и stub, и spy).&#xA;&#xA;24.3 Правила для удобных тестов&#xA;&#xA;зависимости сервиса передавайте через инициализатор (dependency injection);&#xA;не создавайте реальные БД/HTTP-клиенты внутри бизнес-методов;&#xA;для каждого внешнего ресурса сначала определяйте протокол, затем конкретную реализацию и тестовый double;&#xA;в тестах проверяйте наблюдаемое поведение (результат и контракты вызовов), а не внутренние детали реализации.&#xA;&#xA;25. Как правильно работать с тестами&#xA;&#xA;Хорошие тесты в Swift-проекте — это не только assertions, но и понятная структура каталогов, корректные импорты и предсказуемый запуск.&#xA;&#xA;25.1 Размещение тестов&#xA;&#xA;Обычно структура повторяет Sources:&#xA;&#xA;Sources/&#xA;├─ Core/&#xA;│  ├─ Deck/&#xA;│  └─ Review/&#xA;└─ AppCLI/&#xA;&#xA;Tests/&#xA;├─ CoreTests/&#xA;│  ├─ Deck/&#xA;│  │  └─ DeckServiceTests.swift&#xA;│  └─ Review/&#xA;│     └─ ReviewServiceTests.swift&#xA;├─ AppCLITests/&#xA;│  └─ DeckCommandTests.swift&#xA;└─ TestSupport/&#xA;   ├─ Mocks/&#xA;   ├─ Builders/&#xA;   └─ Fixtures/&#xA;&#xA;Плюс такого layout: по имени production-файла сразу понятно, где лежит тест.&#xA;&#xA;25.2 Импорт модулей в тестах&#xA;&#xA;Основные варианты:&#xA;&#xA;import Core — тестируете только публичное API;&#xA;@testable import Core — нужен доступ к internal сущностям target-а.&#xA;&#xA;import Testing&#xA;@testable import Core&#xA;&#xA;@Test&#xA;func createDeckrejectsEmptyName() {&#xA;    let service = DeckService(repository: MockDeckRepository())&#xA;    #expect(throws: DeckError.emptyName) {&#xA;        try service.createDeck(name: &#34;&#34;)&#xA;    }&#xA;}&#xA;&#xA;@testable работает, когда модуль собран с -enable-testing (для swift test&#xA;это обычно уже так).&#xA;&#xA;25.3 Именование тестов&#xA;&#xA;Файл: TypeNameTests.swift (DeckServiceTests.swift);&#xA;Тест: scenarioexpectedResult (createDeckemptyNamethrows);&#xA;Один тест проверяет одну наблюдаемую вещь.&#xA;&#xA;Это упрощает чтение отчёта CI и ускоряет локальную диагностику.&#xA;&#xA;25.4 TestSupport: где держать mocks/fixtures/builders&#xA;&#xA;Mocks/Spies — в Tests/TestSupport/Mocks;&#xA;Fixtures (json/sql/txt sample data) — в Tests/TestSupport/Fixtures;&#xA;Builders/Factories — в Tests/TestSupport/Builders.&#xA;&#xA;Не переносите тестовые helper-ы в Sources, если они не нужны production-коду.&#xA;&#xA;25.5 Запуск и изоляция тестов&#xA;&#xA;Минимальные команды:&#xA;&#xA;swift test&#xA;swift test --filter DeckServiceTests&#xA;swift test --filter createDeckemptyNamethrows&#xA;&#xA;Рекомендации:&#xA;юнит-тесты не должны зависеть от сети/времени/случайности без контроля;&#xA;для времени используйте внедрение Clock/DateProvider через протокол;&#xA;для I/O используйте временные директории и очищайте их в defer.&#xA;&#xA;25.6 Частые проблемы и как избежать&#xA;&#xA;No such module &#39;Core&#39; — target не добавлен в dependencies тестового target-а в Package.swift.&#xA;Недоступен internal символ — нужен @testable import вместо обычного import.&#xA;Хрупкие тесты из-за глобального состояния — убирайте синглтоны из тестового пути, внедряйте зависимости явно.&#xA;&#xA;26. defer: гарантированная очистка и финализация&#xA;&#xA;defer откладывает выполнение блока кода до выхода из текущего scope (функции/метода/замыкания). Это удобно, когда cleanup должен произойти при любом исходе: успешный return, ранний throw или выход по условию.&#xA;&#xA;26.1 Базовая идея&#xA;&#xA;func process() {&#xA;    print(&#34;start&#34;)&#xA;    defer { print(&#34;finish&#34;) }&#xA;    print(&#34;work&#34;)&#xA;}&#xA;&#xA;process()&#xA;// start&#xA;// work&#xA;// finish&#xA;&#xA;defer выполняется один раз, прямо перед выходом из scope.&#xA;&#xA;26.2 defer + ошибки&#xA;&#xA;При throw отложенный блок всё равно выполнится:&#xA;&#xA;enum WorkError: Error { case failed }&#xA;&#xA;func runWork(shouldFail: Bool) throws {&#xA;    defer { print(&#34;cleanup&#34;) }&#xA;    if shouldFail { throw WorkError.failed }&#xA;    print(&#34;success&#34;)&#xA;}&#xA;&#xA;Это делает defer безопасным местом для закрытия ресурсов, unlock и rollback.&#xA;&#xA;26.3 Порядок нескольких defer&#xA;&#xA;Если defer несколько, они выполняются в порядке LIFO (последний добавленный выполняется первым).&#xA;&#xA;func orderDemo() {&#xA;    defer { print(&#34;third&#34;) }&#xA;    defer { print(&#34;second&#34;) }&#xA;    defer { print(&#34;first&#34;) }&#xA;    print(&#34;body&#34;)&#xA;}&#xA;&#xA;orderDemo()&#xA;// body&#xA;// first&#xA;// second&#xA;// third&#xA;&#xA;26.4 Типичные use-cases&#xA;&#xA;закрытие файловых дескрипторов/соединений;&#xA;снятие блокировок (lock/unlock);&#xA;откат временного состояния;&#xA;финальный лог результата операции.&#xA;&#xA;import Foundation&#xA;&#xA;func writeTempFile() throws {&#xA;    let path = &#34;/tmp/swift-defer-demo.txt&#34;&#xA;    FileManager.default.createFile(atPath: path, contents: nil)&#xA;    guard let handle = FileHandle(forWritingAtPath: path) else { return }&#xA;    defer { try? handle.close() }  // выполнится всегда&#xA;&#xA;    try handle.write(contentsOf: Data(&#34;hello\\n&#34;.utf8))&#xA;}&#xA;&#xA;Главное правило: в defer помещайте только завершение/cleanup, а не основную бизнес-логику.&#xA;&#xA;27. Практические правила&#xA;&#xA;Выбирайте let по умолчанию; меняйте данные только через var.&#xA;Предпочитайте struct и перечисления, если не нужна идентичность объекта или наследование.&#xA;Представляйте отсутствие данных через Optional, а ожидаемые сбои — через throws.&#xA;Давайте функциям и аргументам ясные имена; используйте протоколы для требований, а обобщения — для повторно используемого типобезопасного кода.&#xA;Не применяйте ! как замену проверке; не передавайте разделяемое состояние конкурентным задачам без изоляции.&#xA;Держите детали реализации закрытыми: по умолчанию top-level объявления имеют доступ internal; используйте private для локальных деталей и&#xA;  public только для API модуля.&#xA;&#xA;Этот конспект остаётся кратким, но теперь покрывает не только базовый Language Guide, а также ключевые темы Language Reference. Для полной глубины и всех edge-cases смотрите The Swift Programming Language* и&#xA;документацию Swift.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>Краткое введение в повседневный Swift: объявления, типы, функции, коллекции, обработка ошибок, проектирование типов и конкурентность.</p>

<p>Основано на темах</p>

<p><em>The Swift Programming Language, Swift 6.4</em>; примеры ниже написаны специально для этого конспекта.</p>

<p>Swift — компилируемый статически типизированный язык. Компилятор выводит типы из контекста, но проверяет их до запуска программы. Язык старается сделать обычный код безопасным: значения инициализируются до использования, выход за границы коллекции проверяется, отсутствие значения выражается явно через <code>Optional</code>, а ошибки можно обрабатывать типизированно.</p>

<h2 id="1-переменные-константы-и-типы">1. Переменные, константы и типы</h2>

<p>Используйте <code>let</code>, если значение не должно меняться; <code>var</code> — если оно будет переприсваиваться. Тип часто можно не указывать: Swift выведет его из начального значения. Тип можно записать явно после двоеточия.</p>

<pre><code class="language-swift">let title = &#34;Swift&#34;           // String
var attempts: Int = 0
attempts += 1

let price = 19.95             // Double
let isAvailable = true        // Bool
let message = &#34;\(title): \(attempts) attempt(s)&#34;
</code></pre>

<p><code>String</code> — строка,
<code>Int</code> — целое число,
<code>Double</code> — число с плавающей точкой,
<code>Bool</code> — логическое значение.</p>

<p>Swift не выполняет неявное преобразование между разными числовыми типами:</p>

<pre><code class="language-swift">let whole = 4
let fraction = 0.5
let total = Double(whole) + fraction
</code></pre>

<p>Условие в <code>if</code> должно иметь тип <code>Bool</code>; значения вроде <code>1</code> и <code>0</code> не считаются логическими.</p>

<h2 id="2-условия-и-циклы">2. Условия и циклы</h2>

<pre><code class="language-swift">let score = 82

if score &gt;= 90 {
    print(&#34;отлично&#34;)
} else if score &gt;= 60 {
    print(&#34;зачёт&#34;)
} else {
    print(&#34;нужно повторить&#34;)
}

for number in 1...3 {
    print(number)             // 1, 2, 3
}

for index in 0..&lt;3 {
    print(index)              // 0, 1, 2
}
</code></pre>

<p><code>...</code> включает обе границы, <code>..&lt;</code> не включает верхнюю. <code>switch</code> проверяет значение по нескольким шаблонам; каждый непустой вариант должен быть покрыт, обычно через <code>default</code>.</p>

<pre><code class="language-swift">let statusCode = 404

switch statusCode {
case 200:
    print(&#34;успешно&#34;)
case 400..&lt;500:
    print(&#34;ошибка запроса&#34;)
case 500..&lt;600:
    print(&#34;ошибка сервера&#34;)
default:
    print(&#34;другой ответ&#34;)
}
</code></pre>

<p>Для раннего выхода из функции или цикла используйте <code>guard</code>:</p>

<pre><code class="language-swift">func greeting(for name: String?) -&gt; String {
    guard let name, !name.isEmpty else {
        return &#34;Привет!&#34;
    }
    return &#34;Привет, \(name)!&#34;
}
</code></pre>

<p>Как работает <code>guard</code>:</p>
<ul><li>Условие в <code>guard</code> должно быть истинным, чтобы выполнение продолжилось.</li>
<li>Если условие ложно, выполняется ветка <code>else</code>, и из текущего scope нужен обязательный выход: <code>return</code>, <code>throw</code>, <code>break</code>, <code>continue</code> или <code>fatalError</code>.</li>
<li>Значения, извлечённые в <code>guard let</code>, доступны <strong>после</strong> <code>guard</code> в оставшейся части функции, что уменьшает вложенность по сравнению с <code>if let</code>.</li></ul>

<pre><code class="language-swift">enum GuardExampleError: Error {
    case emptyMessage
    case missingRecipient
}

func send(message: String?, to userID: String?) throws {
    guard let message, !message.isEmpty else {
        throw GuardExampleError.emptyMessage
    }
    guard let userID else {
        throw GuardExampleError.missingRecipient
    }

    // message и userID здесь уже не Optional
    print(&#34;Send &#39;\(message)&#39; to \(userID)&#34;)
}
</code></pre>

<h2 id="3-функции-и-замыкания">3. Функции и замыкания</h2>

<p>Функция объявляется через <code>func</code>. Внешние метки аргументов делают вызов читаемым; <code>_</code> убирает метку. Возвращаемый тип указывается после <code>-&gt;</code>.</p>

<pre><code class="language-swift">func convert(_ value: Double, toUnit unit: String = &#34;km&#34;) -&gt; String {
    &#34;\(value) \(unit)&#34;
}

let distance = convert(12.5, toUnit: &#34;mi&#34;)
let defaultDistance = convert(12.5)
</code></pre>

<p>Замыкание — блок кода, который можно сохранить или передать функции. Методы коллекций <code>map</code>, <code>filter</code> и <code>sorted</code> принимают замыкания:</p>

<pre><code class="language-swift">let values = [3, 1, 4, 2]
let squares = values.map { $0 * $0 }
let evenSquares = squares.filter { $0.isMultiple(of: 2) }
let ascending = values.sorted { left, right in left &lt; right }
</code></pre>

<p><code>$0</code>, <code>$1</code> и далее — краткие имена параметров замыкания. Если логика сложная, дайте параметрам имена или вынесите код в отдельную функцию.</p>

<h2 id="4-строки-и-коллекции">4. Строки и коллекции</h2>

<p><code>Array</code> хранит упорядоченный список, <code>Set</code> — уникальные значения без фиксированного порядка, <code>Dictionary</code> связывает ключи со значениями.</p>

<pre><code class="language-swift">var tasks = [&#34;прочитать&#34;, &#34;повторить&#34;]
tasks.append(&#34;попрактиковаться&#34;)

let uniqueTags: Set&lt;String&gt; = [&#34;swift&#34;, &#34;cli&#34;, &#34;swift&#34;]
let scores = [&#34;Ada&#34;: 98, &#34;Linus&#34;: 91]

if let adaScore = scores[&#34;Ada&#34;] {
    print(&#34;Оценка Ada: \(adaScore)&#34;)
}

for task in tasks {
    print(task)
}
</code></pre>

<p>Индекс массива допустим только в диапазоне <code>0..&lt;count</code>; доступ по ключу словаря возвращает optional, поскольку ключа может не быть. Не обращайтесь к индексу, если коллекция может быть пустой.</p>

<p>Строки объединяют интерполяцией (<code>&#34;\(value)&#34;</code>). Многострочные литералы заключаются в <code>&#34;&#34;&#34;</code>; граница отступа задаётся закрывающими кавычками.</p>

<pre><code class="language-swift">let name = &#34;Mina&#34;
let note = &#34;&#34;&#34;
    Участник: \(name)
    Статус: активен
    &#34;&#34;&#34;
</code></pre>

<h2 id="5-optional-и-безопасная-работа-с-отсутствием-значения">5. Optional и безопасная работа с отсутствием значения</h2>

<p><code>Int?</code> — это <code>Optional&lt;Int&gt;</code>: значение либо содержит <code>Int</code>, либо равно <code>nil</code>.
Optional заставляет явно обработать ситуацию, когда данных нет.</p>

<pre><code class="language-swift">let rawAge = &#34;34&#34;
let age = Int(rawAge)                 // Int?

if let age {
    print(&#34;Через год: \(age + 1)&#34;)
} else {
    print(&#34;Возраст не распознан&#34;)
}
</code></pre>

<p><code>if let</code> временно разворачивает optional только в успешной ветке. Для значения по умолчанию используйте <code>??</code>; для последовательного доступа — optional chaining <code>?.</code>.</p>

<pre><code class="language-swift">let nickname: String? = nil
let displayName = nickname ?? &#34;Гость&#34;

struct Profile {
    let email: String?
}

let profile: Profile? = Profile(email: &#34;user@example.org&#34;)
let email = profile?.email            // String?
</code></pre>

<p>Не используйте <code>!</code> для обычного разворачивания: если значения нет, программа аварийно завершится. Принудительное разворачивание уместно только там, где инвариант гарантирован и проверен.</p>

<h2 id="6-ошибки">6. Ошибки</h2>

<p>Для ожидаемых сбоев функция может объявлять <code>throws</code>. Вызов такой функции помечается <code>try</code>, а ошибка обрабатывается через <code>do</code>/<code>catch</code>.</p>

<pre><code class="language-swift">enum InputError: Error {
    case empty
    case notANumber(String)
}

func parseScore(_ text: String) throws -&gt; Int {
    guard !text.isEmpty else { throw InputError.empty }
    guard let score = Int(text) else {
        throw InputError.notANumber(text)
    }
    return score
}

do {
    let score = try parseScore(&#34;87&#34;)
    print(&#34;Баллы: \(score)&#34;)
} catch InputError.empty {
    print(&#34;Введите значение&#34;)
} catch {
    print(&#34;Не удалось разобрать ввод: \(error)&#34;)
}
</code></pre>

<h3 id="проброс-ошибки-через-несколько-функций">Проброс ошибки через несколько функций</h3>

<p>Если функция не может (или не должна) решать, как обрабатывать ошибку, она просто помечается <code>throws</code> и <strong>пробрасывает</strong> ошибку выше по цепочке вызовов.</p>

<pre><code class="language-swift">enum MessagingError: Error {
    case missingRecipient
    case transportOffline
}

func validateRecipient(_ id: String?) throws -&gt; String {
    guard let id, !id.isEmpty else {
        throw MessagingError.missingRecipient
    }
    return id
}

func buildPayload(text: String, recipientID: String?) throws -&gt; [String: String] {
    let recipient = try validateRecipient(recipientID)  // ошибка идёт дальше
    return [&#34;to&#34;: recipient, &#34;text&#34;: text]
}

func performRequest(payload: [String: String]) throws {
    let online = false
    guard online else { throw MessagingError.transportOffline }
    print(&#34;Send payload: \(payload)&#34;)
}

func sendMessage(text: String, recipientID: String?) throws {
    let payload = try buildPayload(text: text, recipientID: recipientID)
    try performRequest(payload: payload)                // проброс на уровень выше
}

do {
    try sendMessage(text: &#34;Hi&#34;, recipientID: nil)
} catch MessagingError.missingRecipient {
    print(&#34;Укажите получателя&#34;)
} catch MessagingError.transportOffline {
    print(&#34;Сеть недоступна, повторите позже&#34;)
} catch {
    print(&#34;Неожиданная ошибка: \(error)&#34;)
}
</code></pre>

<p><code>try?</code> превращает ошибку в <code>nil</code>, если достаточно знать только «получилось/не получилось». <code>try!</code> отключает обработку ошибки и подходит лишь для гарантированно безопасного вызова.</p>

<h2 id="7-свои-типы-struct-enum-class">7. Свои типы: struct, enum, class</h2>

<p><code>struct</code> — тип-значение: при присваивании или передаче копируется само значение. Это хороший выбор по умолчанию для моделей данных. <code>class</code> — ссылочный тип: несколько переменных могут указывать на один экземпляр;
классы поддерживают наследование.</p>

<pre><code class="language-swift">struct Book {
    let title: String
    var page: Int

    mutating func turnPage() {
        page += 1
    }
}

var book = Book(title: &#34;Практика Swift&#34;, page: 1)
book.turnPage()
</code></pre>

<p><code>mutating</code> разрешает методу структуры изменять её свойства. <code>let</code>-экземпляр структуры неизменяем целиком.</p>

<p>Перечисление задаёт конечный набор вариантов. Варианты могут содержать значения; <code>switch</code> позволяет обработать их по типобезопасным шаблонам.</p>

<pre><code class="language-swift">import Foundation

enum DownloadState {
    case idle
    case loading(progress: Double)
    case finished(URL)
    case failed(message: String)
}

func describe(_ state: DownloadState) -&gt; String {
    return switch state {
    case .idle:
        &#34;ожидание&#34;
    case .loading(let progress):
        &#34;загрузка: \(Int(progress * 100))%&#34;
    case .finished(let url):
        &#34;готово: \(url.lastPathComponent)&#34;
    case .failed(let message):
        &#34;ошибка: \(message)&#34;
    }
}
</code></pre>

<p>Здесь <code>URL</code> требует <code>import Foundation</code>.</p>

<h2 id="8-протоколы-расширения-и-обобщения">8. Протоколы, расширения и обобщения</h2>

<p>Протокол описывает требования к типу. Тип принимает эти требования через conformance; общий код может работать с любым подходящим типом.</p>

<pre><code class="language-swift">protocol IdentifiableItem {
    var id: String { get }
    var displayName: String { get }
}

struct Note: IdentifiableItem {
    let id: String
    let displayName: String
}

func printItem&lt;T: IdentifiableItem&gt;(_ item: T) {
    print(&#34;\(item.displayName) [\(item.id)]&#34;)
}
</code></pre>

<p><code>T</code> — параметр типа. Ограничение <code>T: IdentifiableItem</code> гарантирует, что у значения есть нужные свойства. Так сохраняется конкретный тип без приведения к небезопасному <code>Any</code>.</p>

<p>Расширение добавляет вычисляемые свойства и методы существующему типу, не изменяя его исходное объявление:</p>

<pre><code class="language-swift">import Foundation

extension String {
    var isBlank: Bool {
        trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
    }
}
</code></pre>

<p>Этот пример также требует <code>import Foundation</code>.</p>

<h2 id="9-ссылки-и-управление-памятью">9. Ссылки и управление памятью</h2>

<p>Классы управляются ARC (Automatic Reference Counting): объект освобождается, когда на него больше нет сильных ссылок. Сильные циклы ссылок могут удерживать объекты в памяти. Для обратных ссылок применяют <code>weak</code> или <code>unowned</code> с учётом их разных гарантий (<code>weak</code> становится <code>nil</code>, поэтому объявляется optional).</p>

<pre><code class="language-swift">final class Controller {
    var onClose: (() -&gt; Void)?

    func configure() {
        onClose = { [weak self] in
            self?.close()
        }
    }

    private func close() {
        print(&#34;Закрыто&#34;)
    }
}
</code></pre>

<p>Захват <code>[weak self]</code> не даёт замыканию удерживать <code>Controller</code> сильной ссылкой. Для структур и перечислений проблема циклического удержания не возникает так же, как для ссылочных типов.</p>

<h2 id="10-асинхронность-и-конкурентность">10. Асинхронность и конкурентность</h2>

<p><code>async</code> помечает асинхронную функцию, <code>await</code> — точку, где выполнение может приостановиться, пока функция получает результат. Асинхронный вызов обычно не блокирует поток.</p>

<pre><code class="language-swift">func loadTitle() async throws -&gt; String {
    try await Task.sleep(for: .milliseconds(20))
    return &#34;Готово&#34;
}

func showTitle() async {
    do {
        print(try await loadTitle())
    } catch {
        print(&#34;Задача отменена или завершилась с ошибкой&#34;)
    }
}
</code></pre>

<p>Несколько независимых задач можно запустить параллельно через <code>async let</code>;
результат читается с <code>await</code>.
Для динамического количества дочерних задач используют task group. Структурированная конкурентность связывает время жизни дочерних задач с родительской задачей.</p>

<p><code>actor</code> защищает своё изменяемое состояние: доступ к его изолированным свойствам из другого контекста требует <code>await</code>.</p>

<pre><code class="language-swift">actor Counter {
    private var value = 0

    func increment() {
        value += 1
    }

    func currentValue() -&gt; Int {
        value
    }
}

func readCounter() async {
    let counter = Counter()
    await counter.increment()
    let value = await counter.currentValue()
    print(value)
}
</code></pre>

<p>В Swift 6 компилятор строже проверяет передачу данных между конкурентными контекстами. Тип <code>Sendable</code> означает, что значение безопасно передавать между задачами; value-типы с безопасными полями обычно проще сделать потокобезопасными, чем разделяемое изменяемое состояние.</p>

<p>Ниже — самые полезные практические паттерны async-кода.</p>

<h3 id="10-1-параллельные-независимые-запросы-async-let">10.1 Параллельные независимые запросы (<code>async let</code>)</h3>

<p>Если операции не зависят друг от друга, запускайте их одновременно:</p>

<pre><code class="language-swift">func loadProfileName() async throws -&gt; String {
    try await Task.sleep(for: .milliseconds(30))
    return &#34;Alex&#34;
}

func loadUnreadCount() async throws -&gt; Int {
    try await Task.sleep(for: .milliseconds(40))
    return 7
}

func loadDashboard() async throws -&gt; String {
    async let name = loadProfileName()
    async let unread = loadUnreadCount()

    // await здесь дождётся обоих результатов
    return &#34;User: \(try await name), unread: \(try await unread)&#34;
}
</code></pre>

<p><code>async let</code> хорошо подходит для фиксированного числа задач. Если задач динамически много — используйте TaskGroup.</p>

<h3 id="10-2-динамический-fan-out-withthrowingtaskgroup">10.2 Динамический fan-out (<code>withThrowingTaskGroup</code>)</h3>

<p>TaskGroup позволяет создать переменное число дочерних задач и собрать их результаты по мере готовности.</p>

<pre><code class="language-swift">func fetchValue(id: Int) async throws -&gt; Int {
    try await Task.sleep(for: .milliseconds(10))
    return id * id
}

func loadAll(ids: [Int]) async throws -&gt; [Int] {
    try await withThrowingTaskGroup(of: Int.self, returning: [Int].self) { group in
        for id in ids {
            group.addTask {
                try await fetchValue(id: id)
            }
        }

        var result: [Int] = []
        for try await value in group {
            result.append(value)
        }
        return result.sorted()
    }
}
</code></pre>

<p>Если одна дочерняя задача бросит ошибку, <code>withThrowingTaskGroup</code> пробросит её наружу, а остальные дочерние задачи будут отменены.</p>

<h3 id="10-3-отмена-задач-cancellation">10.3 Отмена задач (cancellation)</h3>

<p>Отмена кооперативная: задача должна сама проверять флаг отмены и корректно завершаться.</p>

<pre><code class="language-swift">func longOperation() async throws -&gt; Int {
    var sum = 0
    for i in 1...10_000 {
        if i.isMultiple(of: 500) {
            try Task.checkCancellation()   // бросает CancellationError
            try await Task.sleep(for: .milliseconds(1))
        }
        sum += i
    }
    return sum
}

let task = Task {
    try await longOperation()
}

task.cancel()
</code></pre>

<p><code>defer</code> и <code>withTaskCancellationHandler</code> полезны для очистки ресурсов при досрочном завершении.</p>

<h3 id="10-4-ui-обновления-и-mainactor">10.4 UI-обновления и <code>@MainActor</code></h3>

<p>Код, который трогает состояние UI-модели, лучше изолировать на главном акторе.</p>

<pre><code class="language-swift">@MainActor
final class ViewModel {
    private(set) var title = &#34;Loading...&#34;

    func refresh() async {
        do {
            let text = try await loadTitle()
            title = text
        } catch {
            title = &#34;Error&#34;
        }
    }
}
</code></pre>

<p><code>@MainActor</code> гарантирует последовательный доступ к этому состоянию из конкурентного кода и упрощает reasoning о гонках.</p>

<h3 id="10-5-потоки-данных-asyncsequence">10.5 Потоки данных (<code>AsyncSequence</code>)</h3>

<p><code>for await</code> позволяет читать значения постепенно, как поток событий.</p>

<pre><code class="language-swift">struct CounterStream: AsyncSequence {
    typealias Element = Int

    struct Iterator: AsyncIteratorProtocol {
        var current = 1
        mutating func next() async -&gt; Int? {
            guard current &lt;= 3 else { return nil }
            defer { current += 1 }
            try? await Task.sleep(for: .milliseconds(20))
            return current
        }
    }

    func makeAsyncIterator() -&gt; Iterator { Iterator() }
}

func consumeStream() async {
    for await value in CounterStream() {
        print(&#34;event:&#34;, value)
    }
}
</code></pre>

<p>Такой стиль удобен для логов, сокетов, прогресса загрузки и любых
долгоживущих последовательностей событий.</p>

<h2 id="11-инициализация-и-деинициализация">11. Инициализация и деинициализация</h2>

<p>Инициализатор <code>init</code> подготавливает тип к использованию. Для структур это часто главный способ гарантировать инварианты объекта. У классов дополнительно есть <code>deinit</code>, который вызывается перед освобождением экземпляра ARC.</p>

<pre><code class="language-swift">import Foundation

struct Temperature {
    let celsius: Double

    init(celsius: Double) {
        self.celsius = celsius
    }

    init(fahrenheit: Double) {
        self.celsius = (fahrenheit - 32) * 5 / 9
    }
}

final class FileLogger {
    private let handle: FileHandle

    init?(path: String) {
        guard FileManager.default.createFile(atPath: path, contents: nil) else {
            return nil
        }
        guard let h = FileHandle(forWritingAtPath: path) else { return nil }
        self.handle = h
    }

    func write(_ line: String) throws {
        try handle.write(contentsOf: Data((line + &#34;\n&#34;).utf8))
    }

    deinit {
        try? handle.close()
    }
}
</code></pre>

<p><code>init?</code> — проваливающийся инициализатор (может вернуть <code>nil</code>), полезен для валидации входных данных.</p>

<h2 id="12-type-casting-is-as-as">12. Type Casting (<code>is</code>, <code>as?</code>, <code>as!</code>)</h2>

<p>Когда работа идёт с иерархией классов или массивом значений базового типа, часто нужно узнать реальный тип объекта во время выполнения.</p>

<pre><code class="language-swift">class MediaItem {
    let title: String
    init(title: String) { self.title = title }
}

final class Movie: MediaItem {
    let director: String
    init(title: String, director: String) {
        self.director = director
        super.init(title: title)
    }
}

final class Song: MediaItem {
    let artist: String
    init(title: String, artist: String) {
        self.artist = artist
        super.init(title: title)
    }
}

let library: [MediaItem] = [
    Movie(title: &#34;Blade Runner&#34;, director: &#34;Ridley Scott&#34;),
    Song(title: &#34;Nude&#34;, artist: &#34;Radiohead&#34;)
]

for item in library {
    if let movie = item as? Movie {
        print(&#34;Фильм: \(movie.title), режиссёр \(movie.director)&#34;)
    } else if let song = item as? Song {
        print(&#34;Трек: \(song.title), артист \(song.artist)&#34;)
    }
}
</code></pre>
<ul><li><code>is</code> — проверка типа (<code>item is Movie</code>).</li>
<li><code>as?</code> — безопасное приведение с <code>Optional</code>.</li>
<li><code>as!</code> — принудительное приведение; используйте только при гарантии типа.</li></ul>

<h2 id="13-вложенные-типы-nested-types">13. Вложенные типы (Nested Types)</h2>

<p>Типы можно объявлять внутри других типов, чтобы локализовать детали модели.</p>

<pre><code class="language-swift">struct Payment {
    enum Method {
        case cash
        case card(last4: String)
    }

    struct Receipt {
        let id: String
        let total: Double
    }

    let method: Method
    let amount: Double
    let receipts: [Receipt]
}

let p = Payment(
    method: .card(last4: &#34;1024&#34;),
    amount: 49.9,
    receipts: [
        .init(id: &#34;r-001&#34;, total: 29.9),
        .init(id: &#34;r-002&#34;, total: 20.0)
    ]
)

// Снаружи nested-тип пишется через полное имя:
let allReceipts: [Payment.Receipt] = p.receipts
</code></pre>

<p>Так внешнее API остаётся компактным, а связанные сущности не «засоряют» глобальное пространство имён.</p>

<h2 id="14-opaque-и-boxed-protocol-types-some-и-any">14. Opaque и Boxed protocol types (<code>some</code> и <code>any</code>)</h2>

<p><code>some Protocol</code> скрывает конкретный возвращаемый тип от вызывающего кода, но оставляет его фиксированным для конкретной функции. <code>any Protocol</code> хранит значение неизвестного конкретного типа (type erasure, «коробка»).</p>

<pre><code class="language-swift">protocol Shape {
    func area() -&gt; Double
}

struct Square: Shape {
    let side: Double
    func area() -&gt; Double { side * side }
}

func makeUnitShape() -&gt; some Shape {
    Square(side: 1)
}

func printArea(of shape: any Shape) {
    print(shape.area())
}

let unit = makeUnitShape()      // конкретный тип скрыт
printArea(of: unit)             // принимаем &#34;любую фигуру&#34;
</code></pre>

<p><code>some</code> полезен для стабильного API с сокрытием реализации, <code>any</code> — когда нужно хранить разнородные значения по протоколу в одной коллекции.</p>

<h2 id="15-memory-safety-и-эксклюзивный-доступ">15. Memory Safety и эксклюзивный доступ</h2>

<p>Swift запрещает одновременный конфликтующий доступ к памяти: например, две записи в одно место или чтение+запись, перекрывающиеся по времени.</p>

<pre><code class="language-swift">var balance = 100

func deposit(_ amount: Int, to value: inout Int) {
    value += amount
}

deposit(20, to: &amp;balance)       // OK
</code></pre>

<p>Типичный анти-паттерн — одновременно передавать одну переменную как <code>inout</code> в два параметра одной функции (или читать и писать её же внутри пересекающихся областей). Компилятор и рантайм отслеживают такие конфликты и останавливают опасный код.</p>

<h2 id="16-продвинутые-операторы-фрагмент">16. Продвинутые операторы (фрагмент)</h2>

<p>Swift поддерживает побитовые операции и пользовательские операторы.</p>

<pre><code class="language-swift">let mask: UInt8 = 0b0000_1111
let value: UInt8 = 0b1010_1100
let lowBits = value &amp; mask              // 0b0000_1100
let shifted = value &gt;&gt; 2                // 0b0010_1011
</code></pre>

<p>Пользовательский оператор стоит вводить только когда это реально улучшает читаемость доменной модели:</p>

<pre><code class="language-swift">infix operator **: MultiplicationPrecedence

func ** (lhs: Double, rhs: Double) -&gt; Double {
    pow(lhs, rhs)
}

let x = 2.0 ** 3.0                      // 8.0
</code></pre>

<h2 id="17-макросы-обзор">17. Макросы (обзор)</h2>

<p>Макросы генерируют Swift-код на этапе компиляции и уменьшают шаблонный код. В прикладном коде чаще встречается потребление уже готовых макросов, чем написание своих.</p>

<pre><code class="language-swift">@attached(member, names: named(init))
macro AutoInit()
</code></pre>

<p>Важная мысль: макрос меняет исходный код <em>до</em> проверки типов, поэтому к диагностике и стилю надо относиться так же строго, как к обычному коду.
Для реального проекта макрос обычно подключается из отдельного пакета.</p>

<h2 id="18-language-reference-короткий-ориентир">18. Language Reference: короткий ориентир</h2>

<p>Полный справочник языка значительно формальнее Language Guide. Ниже минимум, который полезно знать в повседневной работе.</p>

<h3 id="18-1-lexical-structure">18.1 Lexical Structure</h3>

<p>Лексика определяет, как исходник разбивается на токены: идентификаторы, литералы, операторы, комментарии, пробелы.</p>

<pre><code class="language-swift">let café = &#34;unicode identifier&#34;
let hex = 0xFF
let scientific = 1.2e3
</code></pre>

<h3 id="18-2-types-expressions-statements">18.2 Types / Expressions / Statements</h3>
<ul><li><strong>Types</strong>: именованные (<code>Int</code>, <code>String</code>) и составные (<code>(Int, String)</code>,
<code>() -&gt; Void</code>, <code>[Int]</code>, <code>[String: Int]</code>, <code>Int?</code>).</li>
<li><strong>Expressions</strong>: вычисление значения (<code>a + b</code>, <code>foo()</code>, <code>x ? y : z</code>).</li>
<li><strong>Statements</strong>: управление потоком (<code>if</code>, <code>for</code>, <code>while</code>, <code>switch</code>, <code>guard</code>,
<code>return</code>, <code>throw</code>, <code>defer</code>).</li></ul>

<pre><code class="language-swift">let pair: (Int, String) = (1, &#34;one&#34;)
let numbers: [Int] = [1, 2, 3]

func execute(_ body: () -&gt; Void) {
    defer { print(&#34;done&#34;) }
    body()
}
</code></pre>

<h3 id="18-3-declarations-attributes-patterns">18.3 Declarations / Attributes / Patterns</h3>
<ul><li><strong>Declarations</strong> вводят имена: <code>let</code>, <code>var</code>, <code>func</code>, <code>struct</code>, <code>class</code>,
<code>enum</code>, <code>protocol</code>, <code>extension</code>, <code>typealias</code>.</li>
<li><strong>Attributes</strong> уточняют поведение декларации или типа:
<code>@discardableResult</code>, <code>@available</code>, <code>@MainActor</code>, <code>@Sendable</code>.</li>
<li><strong>Patterns</strong> используются в <code>switch</code>, <code>if case</code>, <code>guard case</code>, destructuring.</li></ul>

<pre><code class="language-swift">@discardableResult
func log(_ message: String) -&gt; Int {
    print(message)
    return message.count
}

let point = (x: 2, y: 5)
switch point {
case (0, 0):
    print(&#34;origin&#34;)
case let (x, y) where x == y:
    print(&#34;diagonal \(x)&#34;)
default:
    print(&#34;(\(point.x), \(point.y))&#34;)
}
</code></pre>

<h3 id="18-4-generic-parameters-and-arguments">18.4 Generic Parameters and Arguments</h3>

<p>Обобщения объявляют параметры типа (<code>&lt;T&gt;</code>) и ограничения (<code>where</code>), а при вызове компилятор подставляет конкретные аргументы типа.</p>

<pre><code class="language-swift">func firstMatch&lt;T&gt;(in values: [T], where predicate: (T) -&gt; Bool) -&gt; T? {
    for v in values where predicate(v) {
        return v
    }
    return nil
}

let match = firstMatch(in: [10, 15, 20]) { $0.isMultiple(of: 3) } // 15
</code></pre>

<h3 id="18-5-summary-of-grammar">18.5 Summary of Grammar</h3>

<p>Грамматика в книге — формальная EBNF-нотация для полного синтаксиса Swift.</p>

<p>Практически она нужна, когда вы:</p>
<ul><li>разбираете сложный синтаксис из diagnostics компилятора;</li>
<li>пишете инструменты (lint/format/parser);</li>
<li>уточняете граничный случай языка.</li></ul>

<h2 id="19-пакеты-зависимости-форматирование-и-makefile">19. Пакеты, зависимости, форматирование и Makefile</h2>

<p>Даже в небольшом CLI-проекте лучше сразу держать единый workflow: зависимости через SwiftPM, одинаковое форматирование и повторяемые команды сборки.</p>

<h3 id="19-1-как-добавлять-dependencies-через-swiftpm">19.1 Как добавлять dependencies через SwiftPM</h3>

<p>В <code>Package.swift</code> зависимость объявляется в секции <code>dependencies</code>, а затем подключается к нужному target в <code>targets[].dependencies</code>.</p>

<pre><code class="language-swift">// swift-tools-version: 6.4
import PackageDescription

let package = Package(
    name: &#34;demo-cli&#34;,
    platforms: [.macOS(.v13)],
    products: [
        .executable(name: &#34;demo-cli&#34;, targets: [&#34;demo-cli&#34;])
    ],
    dependencies: [
        .package(url: &#34;https://github.com/apple/swift-argument-parser&#34;, from: &#34;1.8.2&#34;)
    ],
    targets: [
        .executableTarget(
            name: &#34;demo-cli&#34;,
            dependencies: [
                .product(name: &#34;ArgumentParser&#34;, package: &#34;swift-argument-parser&#34;)
            ]
        )
    ]
)
</code></pre>

<p>Базовые команды:</p>

<pre><code class="language-bash">swift package resolve        # резолв зависимостей и запись Package.resolved
swift build                  # сборка
swift test                   # тесты
swift package update         # обновить версии в допустимых границах
</code></pre>

<p>Практика:
– фиксируйте минимально нужную версию (<code>from:</code>), не «слишком новую заранее»;
– коммитьте <code>Package.resolved</code> для воспроизводимых сборок;
– после добавления dependency сразу проверяйте <code>swift build</code> и <code>swift test</code>.</p>

<h3 id="19-2-форматирование-и-линтинг-swift-format">19.2 Форматирование и линтинг (<code>swift-format</code>)</h3>

<p>Обычно используют <code>swift-format</code> c конфигом в корне проекта.</p>

<p>Пример <code>.swift-format</code>:</p>

<pre><code class="language-json">{
  &#34;version&#34;: 1,
  &#34;indentation&#34;: { &#34;spaces&#34;: 4 },
  &#34;lineLength&#34;: 120
}
</code></pre>

<p>Полезные команды:</p>

<pre><code class="language-bash">swift format lint --strict --recursive Sources Tests
swift format format --in-place --recursive Sources Tests
</code></pre>
<ul><li><code>lint</code> проверяет стиль без изменения файлов;</li>
<li><code>format</code> автоматически переписывает код по правилам.</li></ul>

<h3 id="19-3-пример-makefile-для-повседневной-работы">19.3 Пример Makefile для повседневной работы</h3>

<p><code>Makefile</code> убирает «магические» команды из головы и стандартизирует CI/локалку.</p>

<pre><code class="language-makefile">.PHONY: build test lint fmt check release clean docs

build:
	swift build

test:
	swift test

lint:
	swift format lint --strict --recursive Sources Tests

fmt:
	swift format format --in-place --recursive Sources Tests

check: build test lint

release:
	swift build -c release

docs:
	swift package --allow-writing-to-package-directory generate-docc-reference

clean:
	swift package clean
</code></pre>

<p>Минимальный цикл перед коммитом:</p>

<pre><code class="language-bash">make check
</code></pre>

<p>Для релиза:</p>

<pre><code class="language-bash">make release
</code></pre>

<p>Если проект использует дополнительные инструменты (например, <code>swiftlint</code>), их добавляют отдельными целями (<code>lint-swiftlint</code>, <code>lint-all</code>) без смешивания с базовым <code>swift format lint</code>.</p>

<h2 id="20-как-правильно-организовать-структуру-проекта">20. Как правильно организовать структуру проекта</h2>

<p>Хорошая структура проекта решает три задачи: понятная навигация, независимое тестирование и безопасное расширение без «комка» зависимостей.</p>

<h3 id="20-1-базовая-структура-swiftpm-проекта">20.1 Базовая структура SwiftPM-проекта</h3>

<p>Для CLI и библиотек удобен стандарт SwiftPM:</p>

<pre><code class="language-text">my-project/
├─ Package.swift
├─ Package.resolved
├─ README.md
├─ Sources/
│  ├─ AppCLI/                  # executable target (точка входа)
│  │  ├─ main.swift            # или @main type
│  │  ├─ Commands/
│  │  │  ├─ RootCommand.swift
│  │  │  ├─ DeckCommand.swift
│  │  │  └─ CardCommand.swift
│  │  └─ Support/
│  │     └─ Output.swift
│  └─ Core/                    # library target с бизнес-логикой
│     ├─ Domain/
│     │  ├─ Deck.swift
│     │  └─ Card.swift
│     ├─ Services/
│     │  ├─ SchedulerService.swift
│     │  └─ ReviewService.swift
│     └─ Persistence/
│        ├─ Database.swift
│        └─ Repositories/
├─ Tests/
│  ├─ CoreTests/
│  └─ AppCLITests/
├─ docs/
└─ Makefile
</code></pre>

<p>Идея: <code>AppCLI</code> занимается только парсингом аргументов/выводом, а вся логика живёт в <code>Core</code>. Это упрощает тесты и повторное использование кода.</p>

<h3 id="20-2-принцип-слои-и-зависимости-в-одну-сторону">20.2 Принцип «слои и зависимости в одну сторону»</h3>

<p>Хорошее правило для модулей:</p>
<ul><li><code>Domain</code> не зависит от CLI/БД/сети;</li>
<li><code>Services</code> зависят от <code>Domain</code>;</li>
<li><code>Persistence</code> реализует протоколы (репозитории/хранилища);</li>
<li><code>AppCLI</code> зависит от <code>Core</code>, но не наоборот.</li></ul>

<p>Такой граф зависимостей предотвращает циклы и делает замену инфраструктуры (например, SQLite → Postgres, CLI → HTTP API) локальной задачей.</p>

<h3 id="20-3-группировка-по-feature-vs-по-техническим-слоям">20.3 Группировка по feature vs по техническим слоям</h3>

<p>Есть два рабочих подхода:</p>
<ul><li><strong>По слоям</strong> (<code>Domain/Services/Persistence</code>) — проще для небольших и средних
проектов.</li>
<li><strong>По фичам</strong> (<code>Deck/Review/Import</code>) — удобнее, когда команд и сценариев становится много.</li></ul>

<p>Компромисс: в <code>Core</code> держать feature-папки, а внутри каждой — мини-слои.</p>

<pre><code class="language-text">Sources/Core/
├─ Deck/
│  ├─ Deck.swift
│  ├─ DeckService.swift
│  └─ DeckRepository.swift
└─ Review/
   ├─ Review.swift
   ├─ ReviewService.swift
   └─ ReviewRepository.swift
</code></pre>

<h3 id="20-4-тестовая-структура">20.4 Тестовая структура</h3>
<ul><li><code>Tests/CoreTests</code> — юнит-тесты чистой логики;</li>
<li><code>Tests/AppCLITests</code> — проверка команд, аргументов и текстового вывода;</li>
<li>общие тестовые фикстуры: <code>Tests/TestSupport</code> (builders, mocks, sample data).</li></ul>

<p>Полезный принцип: тесты должны повторять структуру <code>Sources</code>, чтобы нужный тест находился по имени файла/фичи за несколько секунд.</p>

<h3 id="20-5-именование-файлов-и-типов">20.5 Именование файлов и типов</h3>
<ul><li>Один основной тип на файл (<code>DeckService.swift</code> → <code>DeckService</code>);</li>
<li>суффиксы по роли: <code>...Service</code>, <code>...Repository</code>, <code>...Command</code>;</li>
<li>протоколы рядом с use-case (<code>DeckRepository</code> + <code>SQLiteDeckRepository</code>);</li>
<li>не смешивайте в одном файле CLI-команды и предметную логику.</li></ul>

<h3 id="20-6-что-часто-ломает-структуру">20.6 Что часто ломает структуру</h3>
<ul><li>«Утечка» <code>Foundation</code>/SQL/HTTP в domain-модели без необходимости;</li>
<li>создание god-file вроде <code>Utils.swift</code> или <code>Helpers.swift</code> с десятками
несвязанных функций;</li>
<li>дублирование моделей отдельно для CLI и core без чёткой причины;</li>
<li>циклические импорты между target-ами.</li></ul>

<p>Если сомневаетесь, выносите не по «красоте», а по <strong>границам изменений</strong>: код, который изменяется вместе, должен лежать рядом.</p>

<h3 id="20-7-как-appcli-видит-core-и-что-делать-с-одинаковыми-именами">20.7 Как <code>AppCLI</code> видит <code>Core</code> и что делать с одинаковыми именами</h3>

<p><code>AppCLI</code> «видит» <code>Core</code>, только если зависимость явно объявлена в
<code>Package.swift</code>, а в коде есть <code>import Core</code>.</p>

<pre><code class="language-swift">// Package.swift (фрагмент)
targets: [
    .target(name: &#34;Core&#34;),
    .executableTarget(
        name: &#34;AppCLI&#34;,
        dependencies: [&#34;Core&#34;]
    )
]
</code></pre>

<pre><code class="language-swift">// Sources/AppCLI/main.swift
import Core

let service = DeckService()
</code></pre>

<p>Важно: файлы <code>Deck.swift</code> и <code>Card.swift</code> внутри <strong>одного и того же target</strong> образуют один namespace модуля <code>Core</code>. Поэтому две глобальные функции с одним и тем же именем и сигнатурой вызовут ошибку компиляции, даже если лежат в разных файлах.</p>

<p>Плохой пример (конфликт):</p>

<pre><code class="language-swift">// Deck.swift
func parse(_ text: String) -&gt; Int { 1 }

// Card.swift
func parse(_ text: String) -&gt; Int { 2 } // redeclaration error
</code></pre>

<p>Правильные варианты:</p>
<ul><li>держать функции как методы соответствующих типов;</li>
<li>использовать namespace через <code>enum</code>/<code>struct</code> без состояния;</li>
<li>различать сигнатуры (например, argument labels), если это действительно
разные операции.</li></ul>

<pre><code class="language-swift">enum DeckParser {
    static func parse(_ text: String) -&gt; Int { 1 }
}

enum CardParser {
    static func parse(_ text: String) -&gt; Int { 2 }
}

let a = DeckParser.parse(&#34;...&#34;)
let b = CardParser.parse(&#34;...&#34;)
</code></pre>

<p>Если одинаковые имена приходят из <strong>разных модулей</strong>, обычно помогает явная квалификация через имя модуля/типа, чтобы снять неоднозначность.</p>

<h2 id="21-подключение-внешних-lib-so">21. Подключение внешних <code>lib*.so</code></h2>

<p>На Linux в Swift есть два основных сценария:</p>
<ul><li><strong>статическое подключение API на этапе сборки</strong> (рекомендуется): через
<code>systemLibrary</code> target в SwiftPM;</li>
<li><strong>динамическая загрузка в рантайме</strong>: через <code>dlopen</code>/<code>dlsym</code>.</li></ul>

<h3 id="21-1-рекомендуемый-путь-systemlibrary-modulemap">21.1 Рекомендуемый путь: <code>systemLibrary</code> + modulemap</h3>

<p>Если у вас есть <code>libmylib.so</code> и заголовок <code>mylib.h</code>, подключайте библиотеку как C-модуль.</p>

<p><code>Package.swift</code> (пример):</p>

<pre><code class="language-swift">// swift-tools-version: 6.4
import PackageDescription

let package = Package(
    name: &#34;demo&#34;,
    targets: [
        .systemLibrary(
            name: &#34;CMyLib&#34;,
            pkgConfig: &#34;mylib&#34;
        ),
        .executableTarget(
            name: &#34;app&#34;,
            dependencies: [&#34;CMyLib&#34;],
            linkerSettings: [
                .unsafeFlags([&#34;-L/opt/mylib/lib&#34;]),
                .linkedLibrary(&#34;mylib&#34;)
            ]
        )
    ]
)
</code></pre>

<p><code>Sources/CMyLib/module.modulemap</code>:</p>

<pre><code class="language-text">module CMyLib [system] {
  header &#34;/opt/mylib/include/mylib.h&#34;
  link &#34;mylib&#34;
  export *
}
</code></pre>

<p>Использование в Swift:</p>

<pre><code class="language-swift">import CMyLib

let rc = mylib_init()
print(&#34;init rc =&#34;, rc)
</code></pre>

<p>Что важно:
– библиотека должна иметь <strong>C ABI</strong>;
– если исходник на C++, обычно нужен C-wrapper (<code>extern &#34;C&#34;</code>);
– рантайм должен находить <code>.so</code> (через <code>LD_LIBRARY_PATH</code>, <code>rpath</code> или установку в системный путь линковщика).</p>

<h3 id="21-2-динамическая-загрузка-в-рантайме-dlopen-dlsym">21.2 Динамическая загрузка в рантайме: <code>dlopen</code>/<code>dlsym</code></h3>

<p>Нужна, если библиотека опциональна или выбирается по конфигу во время запуска.</p>

<pre><code class="language-swift">import Glibc

typealias InitFn = @convention(c) () -&gt; Int32

let handle = dlopen(&#34;/opt/mylib/lib/libmylib.so&#34;, RTLD_NOW)
guard handle != nil else {
    fatalError(String(cString: dlerror()))
}
defer { dlclose(handle) }

guard let sym = dlsym(handle, &#34;mylib_init&#34;) else {
    fatalError(String(cString: dlerror()))
}

let fn = unsafeBitCast(sym, to: InitFn.self)
let rc = fn()
print(&#34;init rc =&#34;, rc)
</code></pre>

<p>Минусы этого подхода: меньше проверок на этапе компиляции и выше риск ошибок в подписи функции/символах. Поэтому для обычного проекта предпочтителен <code>systemLibrary</code>.</p>

<h2 id="22-один-проект-lib-so-несколько-утилит">22. Один проект: <code>lib.so</code> + несколько утилит</h2>

<p>Да, в одном SwiftPM-проекте можно собирать сразу несколько артефактов:
динамическую библиотеку и несколько исполняемых файлов, которые от неё зависят.</p>

<h3 id="22-1-пример-package-swift">22.1 Пример <code>Package.swift</code></h3>

<pre><code class="language-swift">// swift-tools-version: 6.4
import PackageDescription

let package = Package(
    name: &#34;my-stack&#34;,
    products: [
        .library(name: &#34;Core&#34;, type: .dynamic, targets: [&#34;Core&#34;]), // libCore.so
        .executable(name: &#34;server&#34;, targets: [&#34;Server&#34;]),
        .executable(name: &#34;client&#34;, targets: [&#34;Client&#34;])
    ],
    targets: [
        .target(name: &#34;Core&#34;),
        .executableTarget(name: &#34;Server&#34;, dependencies: [&#34;Core&#34;]),
        .executableTarget(name: &#34;Client&#34;, dependencies: [&#34;Core&#34;])
    ]
)
</code></pre>

<p>Сборка:</p>

<pre><code class="language-bash">swift build -c release
swift build -c release --product server
swift build -c release --product client
</code></pre>

<p>По умолчанию артефакты лежат в <code>.build/...</code>, а не в <code>./lib</code> и <code>./bin</code>.</p>

<h3 id="22-2-как-получить-layout-lib-bin">22.2 Как получить layout <code>lib/</code> + <code>bin/</code></h3>

<p>Обычно добавляют <code>install</code>-цель в <code>Makefile</code>, которая копирует артефакты после <code>swift build -c release</code>.</p>

<pre><code class="language-makefile">.PHONY: release install

release:
	swift build -c release

install: release
	mkdir -p ./lib ./bin
	cp ./.build/release/libCore.so ./lib/
	cp ./.build/release/server ./bin/
	cp ./.build/release/client ./bin/
</code></pre>

<p>Чтобы <code>server</code>/<code>client</code> нашли <code>libCore.so</code> при запуске, используйте один из вариантов:</p>
<ul><li><code>LD_LIBRARY_PATH=./lib ./bin/server</code></li>
<li>rpath при линковке (предпочтительно для поставки)</li>
<li>установка <code>.so</code> в системный путь линковщика</li></ul>

<h3 id="22-3-рекомендуемая-структура-для-такого-проекта">22.3 Рекомендуемая структура для такого проекта</h3>

<pre><code class="language-text">my-stack/
├─ Package.swift
├─ Sources/
│  ├─ Core/
│  │  ├─ Domain/
│  │  ├─ Services/
│  │  └─ Persistence/
│  ├─ Server/
│  │  └─ main.swift
│  └─ Client/
│     └─ main.swift
├─ Tests/
│  ├─ CoreTests/
│  ├─ ServerTests/
│  └─ ClientTests/
├─ bin/                        # install-артефакты (не из git)
├─ lib/                        # install-артефакты (не из git)
└─ Makefile
</code></pre>

<p><code>Core</code> держит бизнес-логику и контракты, <code>Server</code>/<code>Client</code> — только transport, CLI/сеть и composition root. Это уменьшает дублирование и упрощает тестирование.</p>

<h2 id="23-парсинг-аргументов-cli-команды-и-подкоманды">23. Парсинг аргументов CLI: команды и подкоманды</h2>

<p>Для production CLI в Swift обычно используют
<a href="https://github.com/apple/swift-argument-parser" rel="nofollow"><code>swift-argument-parser</code></a>:
он даёт типобезопасные аргументы, автогенерацию <code>--help</code> и понятную структуру команд.</p>

<h3 id="23-1-базовый-шаблон-команды">23.1 Базовый шаблон команды</h3>

<pre><code class="language-swift">import ArgumentParser

@main
struct App: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: &#34;app&#34;,
        abstract: &#34;Demo CLI&#34;
    )

    @Argument(help: &#34;Имя пользователя&#34;)
    var name: String

    @Option(name: .shortAndLong, help: &#34;Количество повторов&#34;)
    var count: Int = 1

    @Flag(name: .shortAndLong, help: &#34;Подробный вывод&#34;)
    var verbose = false

    mutating func run() throws {
        guard count &gt; 0 else {
            throw ValidationError(&#34;--count must be &gt; 0&#34;)
        }
        for _ in 0..&lt;count {
            print(verbose ? &#34;Hello, \(name) [verbose]&#34; : &#34;Hello, \(name)&#34;)
        }
    }
}
</code></pre>

<p>Здесь:
– <code>@Argument</code> — позиционный аргумент;
– <code>@Option</code> — параметр со значением (<code>--count 3</code>);
– <code>@Flag</code> — булев переключатель (<code>--verbose</code>).</p>

<h3 id="23-2-иерархия-команд-и-подкоманд">23.2 Иерархия команд и подкоманд</h3>

<p>Корневой тип описывает дерево подкоманд через <code>CommandConfiguration</code>.</p>

<pre><code class="language-swift">import ArgumentParser

@main
struct Recall: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: &#34;recall&#34;,
        abstract: &#34;Flashcards CLI&#34;,
        subcommands: [Deck.self, Card.self]
    )
}

struct Deck: ParsableCommand {
    static let configuration = CommandConfiguration(
        abstract: &#34;Manage decks&#34;,
        subcommands: [DeckAdd.self, DeckList.self, DeckDelete.self]
    )
}

struct DeckAdd: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: &#34;add&#34;,
        abstract: &#34;Add deck&#34;
    )

    @Argument(help: &#34;Deck name&#34;)
    var name: String

    mutating func run() throws {
        print(&#34;add deck:&#34;, name)
    }
}
</code></pre>

<p>Так пользователь получает предсказуемый UX:
– <code>recall deck add rust</code>
– <code>recall deck list</code>
– <code>recall card add ...</code></p>

<h3 id="23-3-где-делать-валидацию-аргументов">23.3 Где делать валидацию аргументов</h3>

<p>Есть два основных места:</p>
<ul><li><code>validate()</code> — проверка корректности параметров <strong>до</strong> <code>run()</code>;</li>
<li><code>run()</code> — выполнение команды и бизнес-логика.</li></ul>

<pre><code class="language-swift">struct ImportCommand: ParsableCommand {
    @Option(help: &#34;Path to file&#34;)
    var file: String

    func validate() throws {
        guard file.hasSuffix(&#34;.txt&#34;) else {
            throw ValidationError(&#34;Only .txt files are supported&#34;)
        }
    }

    mutating func run() throws {
        print(&#34;Import from&#34;, file)
    }
}
</code></pre>

<h3 id="23-4-рекомендуемая-структура-файлов-для-cli-команд">23.4 Рекомендуемая структура файлов для CLI-команд</h3>

<pre><code class="language-text">Sources/AppCLI/
├─ main.swift
├─ Commands/
│  ├─ RootCommand.swift
│  ├─ Deck/
│  │  ├─ DeckCommand.swift
│  │  ├─ DeckAddCommand.swift
│  │  ├─ DeckListCommand.swift
│  │  └─ DeckDeleteCommand.swift
│  └─ Card/
│     ├─ CardCommand.swift
│     └─ CardAddCommand.swift
└─ Support/
   ├─ Output.swift
   └─ Parsing/
      └─ CommonOptions.swift
</code></pre>

<p>Правило: корневые команды/подкоманды живут в <code>Commands</code>, переиспользуемые типы аргументов и опций — в <code>Support</code>.</p>

<h2 id="24-mock-в-тестах-как-подменять-зависимости">24. Mock в тестах: как подменять зависимости</h2>

<p>В Swift чаще всего делают mock через протокол и подставную реализацию в тесте.
Это позволяет тестировать бизнес-логику без реальной БД, сети или файловой системы.</p>

<h3 id="24-1-базовый-паттерн-protocol-service-mock">24.1 Базовый паттерн: <code>Protocol + Service + Mock</code></h3>

<pre><code class="language-swift">protocol DeckRepository {
    func save(name: String) throws
}

enum DeckError: Error {
    case emptyName
}

struct DeckService {
    let repository: DeckRepository

    func createDeck(name: String) throws {
        guard !name.isEmpty else { throw DeckError.emptyName }
        try repository.save(name: name)
    }
}
</code></pre>

<p>Mock-реализация для теста:</p>

<pre><code class="language-swift">final class MockDeckRepository: DeckRepository {
    var savedNames: [String] = []
    var saveError: Error?

    func save(name: String) throws {
        if let saveError { throw saveError }
        savedNames.append(name)
    }
}
</code></pre>

<p>Пример теста:</p>

<pre><code class="language-swift">import Testing
@testable import Core

@Test
func createDeck_savesName() throws {
    let mock = MockDeckRepository()
    let service = DeckService(repository: mock)

    try service.createDeck(name: &#34;swift&#34;)

    #expect(mock.savedNames == [&#34;swift&#34;])
}
</code></pre>

<h3 id="24-2-mock-stub-spy-в-чём-разница">24.2 Mock, Stub, Spy — в чём разница</h3>
<ul><li><strong>Stub</strong>: возвращает заранее подготовленный результат.</li>
<li><strong>Mock</strong>: проверяет ожидаемые взаимодействия (вызов, аргументы, количество).</li>
<li><strong>Spy</strong>: записывает факты вызовов, которые проверяются после выполнения.</li></ul>

<p>На практике одна тестовая реализация может совмещать роли (например, <code>MockDeckRepository</code> выше — и stub, и spy).</p>

<h3 id="24-3-правила-для-удобных-тестов">24.3 Правила для удобных тестов</h3>
<ul><li>зависимости сервиса передавайте через инициализатор (dependency injection);</li>
<li>не создавайте реальные БД/HTTP-клиенты внутри бизнес-методов;</li>
<li>для каждого внешнего ресурса сначала определяйте протокол, затем конкретную реализацию и тестовый double;</li>
<li>в тестах проверяйте наблюдаемое поведение (результат и контракты вызовов), а не внутренние детали реализации.</li></ul>

<h2 id="25-как-правильно-работать-с-тестами">25. Как правильно работать с тестами</h2>

<p>Хорошие тесты в Swift-проекте — это не только assertions, но и понятная структура каталогов, корректные импорты и предсказуемый запуск.</p>

<h3 id="25-1-размещение-тестов">25.1 Размещение тестов</h3>

<p>Обычно структура повторяет <code>Sources</code>:</p>

<pre><code class="language-text">Sources/
├─ Core/
│  ├─ Deck/
│  └─ Review/
└─ AppCLI/

Tests/
├─ CoreTests/
│  ├─ Deck/
│  │  └─ DeckServiceTests.swift
│  └─ Review/
│     └─ ReviewServiceTests.swift
├─ AppCLITests/
│  └─ DeckCommandTests.swift
└─ TestSupport/
   ├─ Mocks/
   ├─ Builders/
   └─ Fixtures/
</code></pre>

<p>Плюс такого layout: по имени production-файла сразу понятно, где лежит тест.</p>

<h3 id="25-2-импорт-модулей-в-тестах">25.2 Импорт модулей в тестах</h3>

<p>Основные варианты:</p>
<ul><li><code>import Core</code> — тестируете только публичное API;</li>
<li><code>@testable import Core</code> — нужен доступ к <code>internal</code> сущностям target-а.</li></ul>

<pre><code class="language-swift">import Testing
@testable import Core

@Test
func createDeck_rejectsEmptyName() {
    let service = DeckService(repository: MockDeckRepository())
    #expect(throws: DeckError.emptyName) {
        try service.createDeck(name: &#34;&#34;)
    }
}
</code></pre>

<p><code>@testable</code> работает, когда модуль собран с <code>-enable-testing</code> (для <code>swift test</code>
это обычно уже так).</p>

<h3 id="25-3-именование-тестов">25.3 Именование тестов</h3>
<ul><li>Файл: <code>&lt;TypeName&gt;Tests.swift</code> (<code>DeckServiceTests.swift</code>);</li>
<li>Тест: <code>scenario_expectedResult</code> (<code>createDeck_emptyName_throws</code>);</li>
<li>Один тест проверяет одну наблюдаемую вещь.</li></ul>

<p>Это упрощает чтение отчёта CI и ускоряет локальную диагностику.</p>

<h3 id="25-4-testsupport-где-держать-mocks-fixtures-builders">25.4 TestSupport: где держать mocks/fixtures/builders</h3>
<ul><li><strong>Mocks/Spies</strong> — в <code>Tests/TestSupport/Mocks</code>;</li>
<li><strong>Fixtures</strong> (json/sql/txt sample data) — в <code>Tests/TestSupport/Fixtures</code>;</li>
<li><strong>Builders/Factories</strong> — в <code>Tests/TestSupport/Builders</code>.</li></ul>

<p>Не переносите тестовые helper-ы в <code>Sources</code>, если они не нужны production-коду.</p>

<h3 id="25-5-запуск-и-изоляция-тестов">25.5 Запуск и изоляция тестов</h3>

<p>Минимальные команды:</p>

<pre><code class="language-bash">swift test
swift test --filter DeckServiceTests
swift test --filter createDeck_emptyName_throws
</code></pre>

<p>Рекомендации:
– юнит-тесты не должны зависеть от сети/времени/случайности без контроля;
– для времени используйте внедрение <code>Clock</code>/<code>DateProvider</code> через протокол;
– для I/O используйте временные директории и очищайте их в <code>defer</code>.</p>

<h3 id="25-6-частые-проблемы-и-как-избежать">25.6 Частые проблемы и как избежать</h3>
<ul><li><code>No such module &#39;Core&#39;</code> — target не добавлен в dependencies тестового target-а в <code>Package.swift</code>.</li>
<li>Недоступен <code>internal</code> символ — нужен <code>@testable import</code> вместо обычного <code>import</code>.</li>
<li>Хрупкие тесты из-за глобального состояния — убирайте синглтоны из тестового пути, внедряйте зависимости явно.</li></ul>

<h2 id="26-defer-гарантированная-очистка-и-финализация">26. <code>defer</code>: гарантированная очистка и финализация</h2>

<p><code>defer</code> откладывает выполнение блока кода до выхода из текущего scope (функции/метода/замыкания). Это удобно, когда cleanup должен произойти при любом исходе: успешный <code>return</code>, ранний <code>throw</code> или выход по условию.</p>

<h3 id="26-1-базовая-идея">26.1 Базовая идея</h3>

<pre><code class="language-swift">func process() {
    print(&#34;start&#34;)
    defer { print(&#34;finish&#34;) }
    print(&#34;work&#34;)
}

process()
// start
// work
// finish
</code></pre>

<p><code>defer</code> выполняется один раз, прямо перед выходом из scope.</p>

<h3 id="26-2-defer-ошибки">26.2 <code>defer</code> + ошибки</h3>

<p>При <code>throw</code> отложенный блок всё равно выполнится:</p>

<pre><code class="language-swift">enum WorkError: Error { case failed }

func runWork(shouldFail: Bool) throws {
    defer { print(&#34;cleanup&#34;) }
    if shouldFail { throw WorkError.failed }
    print(&#34;success&#34;)
}
</code></pre>

<p>Это делает <code>defer</code> безопасным местом для закрытия ресурсов, unlock и rollback.</p>

<h3 id="26-3-порядок-нескольких-defer">26.3 Порядок нескольких <code>defer</code></h3>

<p>Если <code>defer</code> несколько, они выполняются в порядке LIFO (последний добавленный выполняется первым).</p>

<pre><code class="language-swift">func orderDemo() {
    defer { print(&#34;third&#34;) }
    defer { print(&#34;second&#34;) }
    defer { print(&#34;first&#34;) }
    print(&#34;body&#34;)
}

orderDemo()
// body
// first
// second
// third
</code></pre>

<h3 id="26-4-типичные-use-cases">26.4 Типичные use-cases</h3>
<ul><li>закрытие файловых дескрипторов/соединений;</li>
<li>снятие блокировок (<code>lock</code>/<code>unlock</code>);</li>
<li>откат временного состояния;</li>
<li>финальный лог результата операции.</li></ul>

<pre><code class="language-swift">import Foundation

func writeTempFile() throws {
    let path = &#34;/tmp/swift-defer-demo.txt&#34;
    FileManager.default.createFile(atPath: path, contents: nil)
    guard let handle = FileHandle(forWritingAtPath: path) else { return }
    defer { try? handle.close() }  // выполнится всегда

    try handle.write(contentsOf: Data(&#34;hello\\n&#34;.utf8))
}
</code></pre>

<p>Главное правило: в <code>defer</code> помещайте только завершение/cleanup, а не основную бизнес-логику.</p>

<h2 id="27-практические-правила">27. Практические правила</h2>
<ul><li>Выбирайте <code>let</code> по умолчанию; меняйте данные только через <code>var</code>.</li>
<li>Предпочитайте <code>struct</code> и перечисления, если не нужна идентичность объекта или наследование.</li>
<li>Представляйте отсутствие данных через <code>Optional</code>, а ожидаемые сбои — через <code>throws</code>.</li>
<li>Давайте функциям и аргументам ясные имена; используйте протоколы для требований, а обобщения — для повторно используемого типобезопасного кода.</li>
<li>Не применяйте <code>!</code> как замену проверке; не передавайте разделяемое состояние конкурентным задачам без изоляции.</li>
<li>Держите детали реализации закрытыми: по умолчанию top-level объявления имеют доступ <code>internal</code>; используйте <code>private</code> для локальных деталей и
<code>public</code> только для API модуля.</li></ul>

<p>Этот конспект остаётся кратким, но теперь покрывает не только базовый Language Guide, а также ключевые темы Language Reference. Для полной глубины и всех edge-cases смотрите <em>The Swift Programming Language</em> и
<a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/" rel="nofollow">документацию Swift</a>.</p>
]]></content:encoded>
      <guid>https://write.rpod.club/devops/swift-kratkii-prakticheskii-spravochnik</guid>
      <pubDate>Mon, 28 Sep 2026 06:18:55 +0000</pubDate>
    </item>
    <item>
      <title>Как я использую Copilot-cli: автоматизация контекста и строгие правила игры</title>
      <link>https://write.rpod.club/devops/kak-ia-ispol-zuiu-copilot-cli-avtomatizatsiia-konteksta-i-strogie-pravila-igry</link>
      <description>&lt;![CDATA[ИИ-ассистенты прочно вошли в нашу рутину, но главная проблема при работе с ними — это потеря контекста и склонность ИИ «фантазировать», переписывая половину проекта ради одной мелкой фичи.&#xA;&#xA;За время работы с Copilot-cli я выработал для себя железобебетонный рабочий процесс, который решает эти проблемы. В этой статье я поделюсь двумя инструментами, которые кардинально меняют качество генерации, и правилами (системным промтом), которые удерживают Copilot в строгих рамках.&#xA;&#xA;Шаг 1. Сначала план, потом код: rfc-cli&#xA;&#xA;Самая частая ошибка — сразу просить Copilot написать код. Без четкого плана ИИ быстро уходит в сторону. Поэтому первое, что я делаю перед тем, как вообще открыть чат и сделать промт — создаю RFC (Request for Comments).&#xA;&#xA;Для этого я использую rfc-cli — отличный консольный инструмент для управления архитектурными решениями прямо в репозитории.&#xA;&#xA;Мой воркфлоу с RFC:&#xA;&#xA;Создание: Я инициализирую новый документ через rfc-cli new.&#xA;Проработка: Набрасываю структуру, цели и изменения.&#xA;Жизненный цикл: Документ строго проходит цепочку статусов: draft → review → accepted → implemented. Пропускать шаги нельзя.&#xA;Валидация: После любых правок я проверяю целостность через rfc-cli check NNNN.&#xA;&#xA;Когда Copilot подключается к задаче, у него перед глазами уже есть утвержденный accepted RFC. Это исключает споры о том, как именно должна работать фича. Кроме того, все RFC хранятся в проекте и всегда есть возможность понять, как и почему было принято каждое решение.&#xA;&#xA;Шаг 2. Борьба с амнезией: mem-cli&#xA;&#xA;Copilot-cli великолепен, но его контекстное окно не резиновое. Стоит начать новую сессию — и он забывает, что мы решили три часа назад, какие особенности у нашей базы данных или какие локальные соглашения мы используем.&#xA;&#xA;Чтобы не терять данные между сессиями, я подключил mem-cli. Это простой, но чертовски эффективный MCP-сервер (Model Context Protocol), который работает как долговременная память.&#xA;&#xA;Каждый раз, когда в процессе работы всплывает важный нюанс (например, &#34;используем только порт 8080&#34; или &#34;компонент X временно не трогаем, он на рефакторинге&#34;), я просто сохраняю этот факт в mem-cli. Copilot обращается к этой памяти и всегда остается в курсе реального положения дел в проекте.&#xA;&#xA;Шаг 3. Свод правил (System Prompt) для Copilot&#xA;&#xA;И последнее по списку, но не по важности — правила игры. Я скормил Copilot жесткий регламент, нарушать который ему строго запрещено. Мой системный промт разделен на три блока:&#xA;&#xA;🛠 Код&#xA;&#xA;Главная беда больших языковых моделей — оверинжиниринг. Чтобы Copilot не &#34;умничал&#34;, я установил для него жесткие рамки:&#xA;&#xA;Минимализм: Любое изменение должно быть минимально необходимым для решения конкретной задачи. Никаких побочных правок в соседних файлах.&#xA;Запрет на самодеятельность: Не изменять архитектуру, не плодить дубликаты компонентов (использовать строго существующие) и не тащить в проект новые библиотеки/зависимости без явной и веской причины.&#xA;Качество: Никакого дублирования кода.&#xA;&#xA;🌿 Git Workflow&#xA;&#xA;Здесь всё автоматизировано до предела. Copilot знает, как я веду ветки и делаю релизы (на примере Rust-проекта):&#xA;&#xA;Никакой работы в main: Все изменения только через ветки. Перед созданием новой ветки от main создается метка с префиксом pre_.&#xA;Дробление задач: Каждая фича — отдельный branch. Каждая подзадача — sub-branch в формате phase-N/X.Y-description.&#xA;Контроль качества: Перед каждым коммитом — обязательный запуск тестов. Перед слиянием в main — полный цикл: build + test + lint. main всегда должен оставаться абсолютно &#34;зеленым&#34;.&#xA;Релиз: Слияние происходит строго с флагом --no-ff. Перед мержем обновляется версия в Cargo.toml, пишется CHANGELOG.md на английском, а после успешного слияния вешается тег vX.Y.Z.&#xA;&#xA;🧠 Стиль работы&#xA;&#xA;Автономия: Я не хочу, чтобы Copilot переспрашивал меня на каждом шаге: &#34;А можно я создам этот файл?&#34;. Он должен работать самостоятельно до полного завершения задачи.&#xA;Осознанность: Перед стартом ИИ обязан прочитать RFC и PLAN (если они есть).&#xA;Порядок: Каждый коммит должен быть с детальным и понятным описанием.&#xA;&#xA;Заключение&#xA;&#xA;Интеграция rfc-cli для планирования, mem-cli для долгосрочной памяти и свода правил для дисциплины превратила Copilot-cli из &#34;продвинутого автодополнения&#34; в полноценного, аккуратного и очень предсказуемого junior-разработчика, который пишет код именно так, как нужно мне.&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>ИИ-ассистенты прочно вошли в нашу рутину, но главная проблема при работе с ними — это потеря контекста и склонность ИИ «фантазировать», переписывая половину проекта ради одной мелкой фичи.</p>

<p>За время работы с <strong>Copilot-cli</strong> я выработал для себя железобебетонный рабочий процесс, который решает эти проблемы. В этой статье я поделюсь двумя инструментами, которые кардинально меняют качество генерации, и правилами (системным промтом), которые удерживают Copilot в строгих рамках.</p>

<h2 id="шаг-1-сначала-план-потом-код-rfc-cli">Шаг 1. Сначала план, потом код: rfc-cli</h2>

<p>Самая частая ошибка — сразу просить Copilot написать код. Без четкого плана ИИ быстро уходит в сторону. Поэтому первое, что я делаю перед тем, как вообще открыть чат и сделать промт — создаю RFC (Request for Comments).</p>

<p>Для этого я использую <a href="https://github.com/classx/rfc-cli" rel="nofollow">rfc-cli</a> — отличный консольный инструмент для управления архитектурными решениями прямо в репозитории.</p>

<h3 id="мой-воркфлоу-с-rfc">Мой воркфлоу с RFC:</h3>
<ol><li><strong>Создание:</strong> Я инициализирую новый документ через <code>rfc-cli new</code>.</li>
<li><strong>Проработка:</strong> Набрасываю структуру, цели и изменения.</li>
<li><strong>Жизненный цикл:</strong> Документ строго проходит цепочку статусов: <code>draft</code> → <code>review</code> → <code>accepted</code> → <code>implemented</code>. Пропускать шаги нельзя.</li>
<li><strong>Валидация:</strong> После любых правок я проверяю целостность через <code>rfc-cli check NNNN</code>.</li></ol>

<p>Когда Copilot подключается к задаче, у него перед глазами уже есть утвержденный <code>accepted</code> RFC. Это исключает споры о том, <em>как именно</em> должна работать фича. Кроме того, все RFC хранятся в проекте и всегда есть возможность понять, как и почему было принято каждое решение.</p>

<h2 id="шаг-2-борьба-с-амнезией-mem-cli">Шаг 2. Борьба с амнезией: mem-cli</h2>

<p>Copilot-cli великолепен, но его контекстное окно не резиновое. Стоит начать новую сессию — и он забывает, что мы решили три часа назад, какие особенности у нашей базы данных или какие локальные соглашения мы используем.</p>

<p>Чтобы не терять данные между сессиями, я подключил <a href="https://github.com/classx/mem-cli" rel="nofollow">mem-cli</a>. Это простой, но чертовски эффективный MCP-сервер (Model Context Protocol), который работает как долговременная память.</p>

<p>Каждый раз, когда в процессе работы всплывает важный нюанс (например, <em>“используем только порт 8080”</em> или <em>“компонент X временно не трогаем, он на рефакторинге”</em>), я просто сохраняю этот факт в <code>mem-cli</code>. Copilot обращается к этой памяти и всегда остается в курсе реального положения дел в проекте.</p>

<h2 id="шаг-3-свод-правил-system-prompt-для-copilot">Шаг 3. Свод правил (System Prompt) для Copilot</h2>

<p>И последнее по списку, но не по важности — правила игры. Я скормил Copilot жесткий регламент, нарушать который ему строго запрещено. Мой системный промт разделен на три блока:</p>

<h3 id="код">🛠 Код</h3>

<p>Главная беда больших языковых моделей — оверинжиниринг. Чтобы Copilot не “умничал”, я установил для него жесткие рамки:</p>
<ul><li><strong>Минимализм:</strong> Любое изменение должно быть минимально необходимым для решения конкретной задачи. Никаких побочных правок в соседних файлах.</li>
<li><strong>Запрет на самодеятельность:</strong> Не изменять архитектуру, не плодить дубликаты компонентов (использовать строго существующие) и не тащить в проект новые библиотеки/зависимости без явной и веской причины.</li>
<li><strong>Качество:</strong> Никакого дублирования кода.</li></ul>

<h3 id="git-workflow">🌿 Git Workflow</h3>

<p>Здесь всё автоматизировано до предела. Copilot знает, как я веду ветки и делаю релизы (на примере Rust-проекта):</p>
<ol><li><strong>Никакой работы в main:</strong> Все изменения только через ветки. Перед созданием новой ветки от <code>main</code> создается метка с префиксом <code>pre_</code>.</li>
<li><strong>Дробление задач:</strong> Каждая фича — отдельный branch. Каждая подзадача — sub-branch в формате <code>phase-N/X.Y-description</code>.</li>
<li><strong>Контроль качества:</strong> Перед каждым коммитом — обязательный запуск тестов. Перед слиянием в <code>main</code> — полный цикл: <code>build</code> + <code>test</code> + <code>lint</code>. <code>main</code> всегда должен оставаться абсолютно “зеленым”.</li>
<li><strong>Релиз:</strong> Слияние происходит строго с флагом <code>--no-ff</code>. Перед мержем обновляется версия в <code>Cargo.toml</code>, пишется CHANGELOG.md на английском, а после успешного слияния вешается тег <code>vX.Y.Z</code>.</li></ol>

<h3 id="стиль-работы">🧠 Стиль работы</h3>
<ul><li><strong>Автономия:</strong> Я не хочу, чтобы Copilot переспрашивал меня на каждом шаге: <em>“А можно я создам этот файл?”</em>. Он должен работать самостоятельно до полного завершения задачи.</li>
<li><strong>Осознанность:</strong> Перед стартом ИИ обязан прочитать RFC и PLAN (если они есть).</li>
<li><strong>Порядок:</strong> Каждый коммит должен быть с детальным и понятным описанием.</li></ul>

<h2 id="заключение">Заключение</h2>

<p>Интеграция <code>rfc-cli</code> для планирования, <code>mem-cli</code> для долгосрочной памяти и свода правил для дисциплины превратила Copilot-cli из “продвинутого автодополнения” в полноценного, аккуратного и очень предсказуемого junior-разработчика, который пишет код именно так, как нужно мне.</p>
]]></content:encoded>
      <guid>https://write.rpod.club/devops/kak-ia-ispol-zuiu-copilot-cli-avtomatizatsiia-konteksta-i-strogie-pravila-igry</guid>
      <pubDate>Thu, 16 Jul 2026 14:06:49 +0000</pubDate>
    </item>
    <item>
      <title>Вот набросок архитектурного дизайн-дока для твоей идеи в текущем «сыром» виде —...</title>
      <link>https://write.rpod.club/devops/vot-nabrosok-arkhitekturnogo-dizain-doka-dlia-tvoei-idei-v-tekushchem-syrom-vide</link>
      <description>&lt;![CDATA[Вот набросок архитектурного дизайн-дока для твоей идеи в текущем «сыром» виде — но уже достаточно структурированном, чтобы по нему можно было делать PoC и дальше эволюционировать.  &#xA;&#xA;1. Цели и мотивация&#xA;&#xA;Использовать модель «машина как сеть независимых ядер», где каждое ядро (или группа ядер) рассматривается как отдельный узел с собственной ОС и минимально разделяемым состоянием, в духе multikernel/Barrelfish. wxdublin.gitbooks&#xA;Дать жёсткую изоляцию и предсказуемую производительность для нескольких AI‑агентов на одной машине, избегая глобального kernel state и межъядерного контеншена. dl.acm&#xA;Позволить иметь разные профили ОС на разных ядрах: «толстый» менеджер с драйверами и сервисами и «тонкие» агент‑ОС, заточенные под конкретный AI‑runtime. multikernel&#xA;&#xA;Нефункциональные цели:  &#xA;Простота reasoning’а (архитектура ближе к распределённой системе, чем к чудовищному монолиту).  &#xA;Возможность поэтапной реализации (сначала на базе Linux+CPU‑isolation/multikernel‑подходов, затем переход к собственному ядру). lpc&#xA;&#xA;2. Модель доверия и роли ядер&#xA;&#xA;2.1. Core 0: Manager OS&#xA;&#xA;Manager‑ядро (одно или маленькая группа ядер) выполняет «системную» ОС: полный стек драйверов, управление памятью, загрузка агент‑ОС, мониторинг и оркестрация. multikernel&#xA;Менеджер владеет всеми глобальными ресурсами: устройствами (NIC, storage, GPU), таблицей распределения физической памяти между ядрами, таблицей живых агент‑ОС и их статусом. en.wikipedia)&#xA;&#xA;2.2. Core 1..N: Agent OS instances&#xA;&#xA;Каждое агент‑ядро (или группа ядер) запускает минималистичную ОС, ориентированную на конкретного AI‑агента/рантайм, со своим rootfs и максимально маленьким TCB. barrelfish&#xA;Агент‑ОС почти не шарит kernel‑состояние с другими ОС; взаимодействие с миром идёт через явные IPC‑каналы c менеджером и, опционально, с другими агентами. sigops&#xA;&#xA;2.3. Trust model&#xA;&#xA;Manager ОС считается корневым TCB системы; агент‑ОС могут быть недоверенными, потенциально падающими/компрометируемыми, их влияние ограничено выделенными ресурсами и IPC‑политикой. allpcb&#xA;В перспективе — возможность запускать агенты с разными уровнями доверия (sandbox, trusted, experimental), с разными квотами и правами на IPC.  &#xA;&#xA;3. Аппаратные предпосылки&#xA;&#xA;SMP/NUMA система (x8664, ARM64, RISC‑V) с поддержкой IOMMU/VT‑d/SMMU для безопасного разделения доступа к устройствам и DMA. bgerofi.github&#xA;Поддержка CPU hotplug и, желательно, memory hotplug либо похожих механизмов — это упрощает динамическое выделение ядер/памяти под новые агент‑ОС, как в современных multikernel‑решениях поверх Linux. lpc&#xA;Стандартная прошивка (UEFI/bootloader), способная загрузить первый менеджер‑kernel, дальше загрузкой остальных ядер и ОС управляет уже менеджер. multikernel&#xA;&#xA;4. Логическая архитектура&#xA;&#xA;4.1. Основные сущности&#xA;&#xA;Manager OS — «главная» ОС на выделенных ядрах.  &#xA;Agent OS — минимальные экземпляры ОС, по одному на каждый выделенный core/группу core.  &#xA;Resource Controller (часть Manager OS) — подсистема учёта и выделения CPU, памяти, I/O.  &#xA;Inter‑OS IPC Fabric — слой обмена сообщениями между Manager и Agent‑ОС (message‑passing поверх shared memory + IPI), по принципам multikernel. wxdublin.gitbooks&#xA;Boot/Loader Service — сервис в Manager OS, который загружает образы Agent‑ОС, назначает им ядра и память.  &#xA;&#xA;4.2. Высокоуровневые принципы (в духе multikernel)&#xA;&#xA;Все межъядерные взаимодействия явные message‑passing, без неявного shared kernel state. dl.acm&#xA;Структура ОС максимально hardware‑neutral: логика не должна зависеть от конкретной топологии NUMA/кэшей, это инкапсулируется в CPU‑drivers/low‑level части. sigops&#xA;Состояние системы рассматривается как распределённое; согласование делается через протоколы (lease, версии, heartbeats), а не через глобальные блокировки. pages.cs.wisc&#xA;&#xA;5. Модель памяти и NUMA&#xA;&#xA;5.1. Разбиение физической памяти&#xA;&#xA;Физическая память делится на:  &#xA;  Region M — память менеджера (код, данные, служебные буферы).  &#xA;  Region Aᵢ — выделенные регионы для каждой Agent‑ОС.  &#xA;  Shared Region S — один или несколько регионов для shared‑данных (модели, общие кэши, IPC‑очереди).  &#xA;Manager выступает как «allocator of last resort» и единственный компонент, который может изменять partitioning (например, через memory hotplug‑подобные механизмы). lpc&#xA;&#xA;5.2. Виртуальная память внутри Agent‑ОС&#xA;&#xA;Agent‑ОС видит свои Region Aᵢ + явные mapping’и в Shared Region S; она не знает о памяти других агентов. dl.acm&#xA;Можно поддерживать два класса mapping’ов:  &#xA;  Long‑lived (например, read‑only модели нейросетей).  &#xA;  Short‑lived (буферы запросов/ответов, временные шары между Agent‑ОС).  &#xA;&#xA;5.3. NUMA‑осведомлённость&#xA;&#xA;При наличии NUMA менеджер старается выделять агент‑ОС память в локальном NUMA‑узле для их ядра, а shared‑регион может быть либо реплицирован, либо размещён в нескольких NUMA‑домейнах. en.wikipedia)&#xA;Политика размещения — часть Resource Controller и может быть настраиваемой.  &#xA;&#xA;6. IPC и протоколы взаимодействия&#xA;&#xA;6.1. Базовый механизм&#xA;&#xA;IPC строится на фиксированного размера сообщениях, доставляемых через shared memory ring‑buffers + сигналы через IPI/doorbells, как в классических multikernel/fast‑path‑driver работах. cs.cmu&#xA;Для каждого Agent‑ОС есть минимум два канала:  &#xA;  Control Channel (Agent ↔ Manager) — конфиг, статус, управление жизненным циклом.  &#xA;  Data Channel(s) — запросы от клиентов (через Manager) и ответы агента.  &#xA;&#xA;6.2. Примеры протоколов&#xA;&#xA;Lifecycle Protocol  &#xA;  SPAWNAGENT(imageid, cores, memsize, policy)  &#xA;  AGENTSTATUS(id, state, metrics)  &#xA;  STOPAGENT(id, reason)  &#xA;&#xA;AI‑Request Protocol  &#xA;  INFERREQUEST(agentid, requestid, inputhandle, outputhandle)  &#xA;  INFERRESULT(requestid, status, outputhandle)  &#xA;&#xA;Где handle указывает на буфер в Shared Region S, чтобы не гонять большие payload’ы по IPC.  &#xA;&#xA;6.3. Расширение до Agent↔Agent&#xA;&#xA;Для некоторых сценариев (агенты‑координаторы) можно разрешить Manager‑mediated Agent↔Agent‑каналы, но физически они всё равно реализованы как shared memory + протокол поверх него. wxdublin.gitbooks&#xA;&#xA;7. Устройства и I/O&#xA;&#xA;7.1. Модель владения устройствами&#xA;&#xA;По умолчанию все физические устройства принадлежат Manager‑ОС: драйвера NIC, storage, GPU находятся там. bgerofi.github&#xA;Agent‑ОС взаимодействуют с сетью и диском через RPC к сервисам Manager‑ОС (сетевой proxy, object storage‑proxy и т.д.). bgerofi.github&#xA;&#xA;7.2. Прямой доступ ускорителей&#xA;&#xA;Для высокопроизводительных сценариев агент‑ОС может получать прямой доступ к «виртуализированным» очередям устройств (например, аппаратные очереди NIC/GPU), аналогично fast‑path‑drivers и Linux+LWK side‑by‑side моделям. bgerofi.github&#xA;Безопасность и разделение обеспечиваются IOMMU и политиками, установленными Manager‑ОС.  &#xA;&#xA;8. Загрузка и жизненный цикл&#xA;&#xA;8.1. Boot sequence&#xA;&#xA;Firmware/bootloader загружает Manager‑kernel на core 0, выделяет ему минимальный набор ресурсов.  &#xA;Manager‑ОС инициализирует IPC fabric, Resource Controller, базовые драйверы устройств. barrelfish&#xA;На основе конфигурации Manager выделяет набор core и memory‑region’ов и запускает Agent‑ОС через свой boot‑сервис (по аналогии с kexec‑подобной многоядерной загрузкой в multikernel‑Linux проектах). multikernel&#xA;Каждая Agent‑ОС регистрируется у Manager по control‑каналу, после чего может принимать рабочие запросы.  &#xA;&#xA;8.2. Обновление и перезапуск&#xA;&#xA;Agent‑ОС можно перезапускать независимо, не трогая Manager и другие агенты (fault isolation).  &#xA;В будущем возможно реализовать zero‑downtime обновление как в multikernel‑roadmap: поднять новый экземпляр Agent‑ОС, мигрировать state, потом убить старый. youtube&#xA;&#xA;9. Сценарии использования (фокус на AI)&#xA;&#xA;LLM‑serving с несколькими моделями/версиями: каждому агенту выделена своя модель и runtime, Manager занимается роутингом запросов и ресурсами. eunomia&#xA;Изолированные «экспериментальные» агенты: research‑модели, несущие риск OOM/undefined поведения, гоняются в отдельных Agent‑ОС, не валя production‑агентов.  &#xA;Гетерогенная нагрузка: часть агент‑ОС может быть оптимизирована под batch‑job’ы, другие — под low‑latency запросы, с разными конфигурациями kernel и scheduler.  &#xA;&#xA;10. План PoC (минимально жизнеспособная версия)&#xA;&#xA;Stage 0: Симуляция на обычном Linux  &#xA;   Использовать CPU isolation (isolcpus, nohzfull, rcunocbs) чтобы выделить несколько ядер под «агентов», а системные сервисы оставить на ядре‑менеджере. docs.kernel&#xA;   Реализовать Manager и Agents как отдельные процессы/контейнеры с явным IPC поверх shared memory и eventfd/signals.  &#xA;&#xA;Stage 1: Multikernel‑подход на Linux  &#xA;   Вдохновиться существующим Multikernel‑Linux проектом: запуск нескольких экземпляров ядра/около‑ядрового окружения на разных ядрах без полноценной виртуализации. lpc&#xA;   Перенести туда концепцию Manager/Agent и IPC‑fabric.  &#xA;&#xA;Stage 2: Специализированное ядро для Agent‑ОС  &#xA;   Срезать из ядра всё лишнее, оставить только необходимое для AI‑runtime + IPC + базовый MM.  &#xA;   Сделать загрузку этих ядёр через менеджера по kexec‑подобной схеме. multikernel&#xA;&#xA;Stage 3: Продвинутое управление ресурсами и обновлениями  &#xA;   Добавить динамическое выделение ядер/памяти под агентов.  &#xA;   Реализовать прототип zero‑downtime обновлений для Agent‑ОС и, возможно, для самого Manager‑ядра. youtube&#xA;&#xA;**&#xA;&#xA;Если хочешь, дальше можно углубиться в один из блоков — например, подробно расписать формат IPC‑сообщений и layout shared‑memory‑колец, или сделать конкретный PoC‑план под твой текущий хост (кол‑во ядер, есть ли IOMMU/GPU, какие модели нужно крутить).]]&gt;</description>
      <content:encoded><![CDATA[<p>Вот набросок архитектурного дизайн-дока для твоей идеи в текущем «сыром» виде — но уже достаточно структурированном, чтобы по нему можно было делать PoC и дальше эволюционировать.</p>

<p>***</p>

<h2 id="1-цели-и-мотивация">1. Цели и мотивация</h2>
<ul><li>Использовать модель «машина как сеть независимых ядер», где каждое ядро (или группа ядер) рассматривается как отдельный узел с собственной ОС и минимально разделяемым состоянием, в духе multikernel/Barrelfish. <a href="https://wxdublin.gitbooks.io/deep-into-linux-and-beyond/content/multikernel.html" rel="nofollow">wxdublin.gitbooks</a></li>
<li>Дать жёсткую изоляцию и предсказуемую производительность для нескольких AI‑агентов на одной машине, избегая глобального kernel state и межъядерного контеншена. <a href="https://dl.acm.org/doi/10.1145/1629575.1629579" rel="nofollow">dl.acm</a></li>
<li>Позволить иметь разные профили ОС на разных ядрах: «толстый» менеджер с драйверами и сервисами и «тонкие» агент‑ОС, заточенные под конкретный AI‑runtime. <a href="https://multikernel.io/roadmap.html" rel="nofollow">multikernel</a></li></ul>

<p>Нефункциональные цели:<br>
– Простота reasoning’а (архитектура ближе к распределённой системе, чем к чудовищному монолиту).<br>
– Возможность поэтапной реализации (сначала на базе Linux+CPU‑isolation/multikernel‑подходов, затем переход к собственному ядру). <a href="https://lpc.events/event/19/contributions/2074/attachments/1776/3957/Multikernel-LPC2025.pdf" rel="nofollow">lpc</a></p>

<p>***</p>

<h2 id="2-модель-доверия-и-роли-ядер">2. Модель доверия и роли ядер</h2>

<h3 id="2-1-core-0-manager-os">2.1. Core 0: Manager OS</h3>
<ul><li>Manager‑ядро (одно или маленькая группа ядер) выполняет «системную» ОС: полный стек драйверов, управление памятью, загрузка агент‑ОС, мониторинг и оркестрация. <a href="https://multikernel.io/roadmap.html" rel="nofollow">multikernel</a></li>
<li>Менеджер владеет всеми глобальными ресурсами: устройствами (NIC, storage, GPU), таблицей распределения физической памяти между ядрами, таблицей живых агент‑ОС и их статусом. <a href="https://en.wikipedia.org/wiki/Barrelfish_(operating_system)" rel="nofollow">en.wikipedia</a></li></ul>

<h3 id="2-2-core-1-n-agent-os-instances">2.2. Core 1..N: Agent OS instances</h3>
<ul><li>Каждое агент‑ядро (или группа ядер) запускает минималистичную ОС, ориентированную на конкретного AI‑агента/рантайм, со своим rootfs и максимально маленьким TCB. <a href="https://barrelfish.org" rel="nofollow">barrelfish</a></li>
<li>Агент‑ОС почти не шарит kernel‑состояние с другими ОС; взаимодействие с миром идёт через явные IPC‑каналы c менеджером и, опционально, с другими агентами. <a href="https://www.sigops.org/s/conferences/sosp/2009/papers/baumann-sosp09.pdf" rel="nofollow">sigops</a></li></ul>

<h3 id="2-3-trust-model">2.3. Trust model</h3>
<ul><li>Manager ОС считается корневым TCB системы; агент‑ОС могут быть недоверенными, потенциально падающими/компрометируемыми, их влияние ограничено выделенными ресурсами и IPC‑политикой. <a href="https://www.allpcb.com/allelectrohub/operating-system-intrinsic-security-for-multikernel-systems" rel="nofollow">allpcb</a></li>
<li>В перспективе — возможность запускать агенты с разными уровнями доверия (sandbox, trusted, experimental), с разными квотами и правами на IPC.<br></li></ul>

<p>***</p>

<h2 id="3-аппаратные-предпосылки">3. Аппаратные предпосылки</h2>
<ul><li>SMP/NUMA система (x86_64, ARM64, RISC‑V) с поддержкой IOMMU/VT‑d/SMMU для безопасного разделения доступа к устройствам и DMA. <a href="https://bgerofi.github.io/papers/bgerofi-hpdc18.pdf" rel="nofollow">bgerofi.github</a></li>
<li>Поддержка CPU hotplug и, желательно, memory hotplug либо похожих механизмов — это упрощает динамическое выделение ядер/памяти под новые агент‑ОС, как в современных multikernel‑решениях поверх Linux. <a href="https://lpc.events/event/19/contributions/2074/attachments/1776/3957/Multikernel-LPC2025.pdf" rel="nofollow">lpc</a></li>
<li>Стандартная прошивка (UEFI/bootloader), способная загрузить первый менеджер‑kernel, дальше загрузкой остальных ядер и ОС управляет уже менеджер. <a href="https://multikernel.io/roadmap.html" rel="nofollow">multikernel</a></li></ul>

<p>***</p>

<h2 id="4-логическая-архитектура">4. Логическая архитектура</h2>

<h3 id="4-1-основные-сущности">4.1. Основные сущности</h3>
<ul><li><strong>Manager OS</strong> — «главная» ОС на выделенных ядрах.<br></li>
<li><strong>Agent OS</strong> — минимальные экземпляры ОС, по одному на каждый выделенный core/группу core.<br></li>
<li><strong>Resource Controller</strong> (часть Manager OS) — подсистема учёта и выделения CPU, памяти, I/O.<br></li>
<li><strong>Inter‑OS IPC Fabric</strong> — слой обмена сообщениями между Manager и Agent‑ОС (message‑passing поверх shared memory + IPI), по принципам multikernel. <a href="https://wxdublin.gitbooks.io/deep-into-linux-and-beyond/content/multikernel.html" rel="nofollow">wxdublin.gitbooks</a></li>
<li><strong>Boot/Loader Service</strong> — сервис в Manager OS, который загружает образы Agent‑ОС, назначает им ядра и память.<br></li></ul>

<h3 id="4-2-высокоуровневые-принципы-в-духе-multikernel">4.2. Высокоуровневые принципы (в духе multikernel)</h3>
<ul><li>Все межъядерные взаимодействия явные message‑passing, без неявного shared kernel state. <a href="https://dl.acm.org/doi/10.1145/1629575.1629579" rel="nofollow">dl.acm</a></li>
<li>Структура ОС максимально hardware‑neutral: логика не должна зависеть от конкретной топологии NUMA/кэшей, это инкапсулируется в CPU‑drivers/low‑level части. <a href="https://www.sigops.org/s/conferences/sosp/2009/papers/baumann-sosp09.pdf" rel="nofollow">sigops</a></li>
<li>Состояние системы рассматривается как распределённое; согласование делается через протоколы (lease, версии, heartbeats), а не через глобальные блокировки. <a href="https://pages.cs.wisc.edu/~swift/classes/cs736-sp15/blog/2015/02/the_multikernel_a_new_os_archi.html" rel="nofollow">pages.cs.wisc</a></li></ul>

<p>***</p>

<h2 id="5-модель-памяти-и-numa">5. Модель памяти и NUMA</h2>

<h3 id="5-1-разбиение-физической-памяти">5.1. Разбиение физической памяти</h3>
<ul><li>Физическая память делится на:<br>
<ul><li><strong>Region M</strong> — память менеджера (код, данные, служебные буферы).<br></li>
<li><strong>Region Aᵢ</strong> — выделенные регионы для каждой Agent‑ОС.<br></li>
<li><strong>Shared Region S</strong> — один или несколько регионов для shared‑данных (модели, общие кэши, IPC‑очереди).<br></li></ul></li>
<li>Manager выступает как «allocator of last resort» и единственный компонент, который может изменять partitioning (например, через memory hotplug‑подобные механизмы). <a href="https://lpc.events/event/19/contributions/2074/attachments/1776/3957/Multikernel-LPC2025.pdf" rel="nofollow">lpc</a></li></ul>

<h3 id="5-2-виртуальная-память-внутри-agent-ос">5.2. Виртуальная память внутри Agent‑ОС</h3>
<ul><li>Agent‑ОС видит свои Region Aᵢ + явные mapping’и в Shared Region S; она не знает о памяти других агентов. <a href="https://dl.acm.org/doi/10.1145/1629575.1629579" rel="nofollow">dl.acm</a></li>
<li>Можно поддерживать два класса mapping’ов:<br>
<ul><li>Long‑lived (например, read‑only модели нейросетей).<br></li>
<li>Short‑lived (буферы запросов/ответов, временные шары между Agent‑ОС).<br></li></ul></li></ul>

<h3 id="5-3-numa-осведомлённость">5.3. NUMA‑осведомлённость</h3>
<ul><li>При наличии NUMA менеджер старается выделять агент‑ОС память в локальном NUMA‑узле для их ядра, а shared‑регион может быть либо реплицирован, либо размещён в нескольких NUMA‑домейнах. <a href="https://en.wikipedia.org/wiki/Barrelfish_(operating_system)" rel="nofollow">en.wikipedia</a></li>
<li>Политика размещения — часть Resource Controller и может быть настраиваемой.<br></li></ul>

<p>***</p>

<h2 id="6-ipc-и-протоколы-взаимодействия">6. IPC и протоколы взаимодействия</h2>

<h3 id="6-1-базовый-механизм">6.1. Базовый механизм</h3>
<ul><li>IPC строится на фиксированного размера сообщениях, доставляемых через shared memory ring‑buffers + сигналы через IPI/doorbells, как в классических multikernel/fast‑path‑driver работах. <a href="https://www.cs.cmu.edu/afs/cs/academic/class/15740-s17/www/papers/bbetal09.pdf" rel="nofollow">cs.cmu</a></li>
<li>Для каждого Agent‑ОС есть минимум два канала:<br>
<ul><li><strong>Control Channel</strong> (Agent ↔ Manager) — конфиг, статус, управление жизненным циклом.<br></li>
<li><strong>Data Channel(s)</strong> — запросы от клиентов (через Manager) и ответы агента.<br></li></ul></li></ul>

<h3 id="6-2-примеры-протоколов">6.2. Примеры протоколов</h3>
<ul><li><p><strong>Lifecycle Protocol</strong></p>
<ul><li><code>SPAWN_AGENT(image_id, cores, mem_size, policy)</code><br></li>
<li><code>AGENT_STATUS(id, state, metrics)</code><br></li>
<li><code>STOP_AGENT(id, reason)</code><br></li></ul></li>

<li><p><strong>AI‑Request Protocol</strong></p>
<ul><li><code>INFER_REQUEST(agent_id, request_id, input_handle, output_handle)</code><br></li>
<li><code>INFER_RESULT(request_id, status, output_handle)</code><br></li></ul></li></ul>

<p>Где <code>*_handle</code> указывает на буфер в Shared Region S, чтобы не гонять большие payload’ы по IPC.</p>

<h3 id="6-3-расширение-до-agent-agent">6.3. Расширение до Agent↔Agent</h3>
<ul><li>Для некоторых сценариев (агенты‑координаторы) можно разрешить Manager‑mediated Agent↔Agent‑каналы, но физически они всё равно реализованы как shared memory + протокол поверх него. <a href="https://wxdublin.gitbooks.io/deep-into-linux-and-beyond/content/multikernel.html" rel="nofollow">wxdublin.gitbooks</a></li></ul>

<p>***</p>

<h2 id="7-устройства-и-i-o">7. Устройства и I/O</h2>

<h3 id="7-1-модель-владения-устройствами">7.1. Модель владения устройствами</h3>
<ul><li>По умолчанию все физические устройства принадлежат Manager‑ОС: драйвера NIC, storage, GPU находятся там. <a href="https://bgerofi.github.io/papers/bgerofi-hpdc18.pdf" rel="nofollow">bgerofi.github</a></li>
<li>Agent‑ОС взаимодействуют с сетью и диском через RPC к сервисам Manager‑ОС (сетевой proxy, object storage‑proxy и т.д.). <a href="https://bgerofi.github.io/papers/bgerofi-hpdc18.pdf" rel="nofollow">bgerofi.github</a></li></ul>

<h3 id="7-2-прямой-доступ-ускорителей">7.2. Прямой доступ ускорителей</h3>
<ul><li>Для высокопроизводительных сценариев агент‑ОС может получать прямой доступ к «виртуализированным» очередям устройств (например, аппаратные очереди NIC/GPU), аналогично fast‑path‑drivers и Linux+LWK side‑by‑side моделям. <a href="https://bgerofi.github.io/papers/bgerofi-hpdc18.pdf" rel="nofollow">bgerofi.github</a></li>
<li>Безопасность и разделение обеспечиваются IOMMU и политиками, установленными Manager‑ОС.<br></li></ul>

<p>***</p>

<h2 id="8-загрузка-и-жизненный-цикл">8. Загрузка и жизненный цикл</h2>

<h3 id="8-1-boot-sequence">8.1. Boot sequence</h3>
<ol><li>Firmware/bootloader загружает Manager‑kernel на core 0, выделяет ему минимальный набор ресурсов.<br></li>
<li>Manager‑ОС инициализирует IPC fabric, Resource Controller, базовые драйверы устройств. <a href="https://barrelfish.org" rel="nofollow">barrelfish</a></li>
<li>На основе конфигурации Manager выделяет набор core и memory‑region’ов и запускает Agent‑ОС через свой boot‑сервис (по аналогии с kexec‑подобной многоядерной загрузкой в multikernel‑Linux проектах). <a href="https://multikernel.io/roadmap.html" rel="nofollow">multikernel</a></li>
<li>Каждая Agent‑ОС регистрируется у Manager по control‑каналу, после чего может принимать рабочие запросы.<br></li></ol>

<h3 id="8-2-обновление-и-перезапуск">8.2. Обновление и перезапуск</h3>
<ul><li>Agent‑ОС можно перезапускать независимо, не трогая Manager и другие агенты (fault isolation).<br></li>
<li>В будущем возможно реализовать zero‑downtime обновление как в multikernel‑roadmap: поднять новый экземпляр Agent‑ОС, мигрировать state, потом убить старый. <a href="https://www.youtube.com/watch?v=EvzaXWv3-00" rel="nofollow">youtube</a></li></ul>

<p>***</p>

<h2 id="9-сценарии-использования-фокус-на-ai">9. Сценарии использования (фокус на AI)</h2>
<ul><li><strong>LLM‑serving с несколькими моделями/версиями</strong>: каждому агенту выделена своя модель и runtime, Manager занимается роутингом запросов и ресурсами. <a href="https://eunomia.dev/blog/2025/02/18/os-level-challenges-in-llm-inference-and-optimizations/" rel="nofollow">eunomia</a></li>
<li><strong>Изолированные «экспериментальные» агенты</strong>: research‑модели, несущие риск OOM/undefined поведения, гоняются в отдельных Agent‑ОС, не валя production‑агентов.<br></li>
<li><strong>Гетерогенная нагрузка</strong>: часть агент‑ОС может быть оптимизирована под batch‑job’ы, другие — под low‑latency запросы, с разными конфигурациями kernel и scheduler.<br></li></ul>

<p>***</p>

<h2 id="10-план-poc-минимально-жизнеспособная-версия">10. План PoC (минимально жизнеспособная версия)</h2>
<ol><li><p><strong>Stage 0: Симуляция на обычном Linux</strong></p>
<ul><li>Использовать CPU isolation (<code>isolcpus</code>, <code>nohz_full</code>, <code>rcu_nocbs</code>) чтобы выделить несколько ядер под «агентов», а системные сервисы оставить на ядре‑менеджере. <a href="https://docs.kernel.org/admin-guide/cpu-isolation.html" rel="nofollow">docs.kernel</a></li>
<li>Реализовать Manager и Agents как отдельные процессы/контейнеры с явным IPC поверх shared memory и <code>eventfd</code>/signals.<br></li></ul></li>

<li><p><strong>Stage 1: Multikernel‑подход на Linux</strong></p>
<ul><li>Вдохновиться существующим Multikernel‑Linux проектом: запуск нескольких экземпляров ядра/около‑ядрового окружения на разных ядрах без полноценной виртуализации. <a href="https://lpc.events/event/19/contributions/2074/attachments/1776/3957/Multikernel-LPC2025.pdf" rel="nofollow">lpc</a></li>
<li>Перенести туда концепцию Manager/Agent и IPC‑fabric.<br></li></ul></li>

<li><p><strong>Stage 2: Специализированное ядро для Agent‑ОС</strong></p>
<ul><li>Срезать из ядра всё лишнее, оставить только необходимое для AI‑runtime + IPC + базовый MM.<br></li>
<li>Сделать загрузку этих ядёр через менеджера по kexec‑подобной схеме. <a href="https://multikernel.io/roadmap.html" rel="nofollow">multikernel</a></li></ul></li>

<li><p><strong>Stage 3: Продвинутое управление ресурсами и обновлениями</strong></p>
<ul><li>Добавить динамическое выделение ядер/памяти под агентов.<br></li>
<li>Реализовать прототип zero‑downtime обновлений для Agent‑ОС и, возможно, для самого Manager‑ядра. <a href="https://www.youtube.com/watch?v=EvzaXWv3-00" rel="nofollow">youtube</a></li></ul></li></ol>

<p>***</p>

<p>Если хочешь, дальше можно углубиться в один из блоков — например, подробно расписать формат IPC‑сообщений и layout shared‑memory‑колец, или сделать конкретный PoC‑план под твой текущий хост (кол‑во ядер, есть ли IOMMU/GPU, какие модели нужно крутить).</p>
]]></content:encoded>
      <guid>https://write.rpod.club/devops/vot-nabrosok-arkhitekturnogo-dizain-doka-dlia-tvoei-idei-v-tekushchem-syrom-vide</guid>
      <pubDate>Wed, 03 Jun 2026 12:37:07 +0000</pubDate>
    </item>
    <item>
      <title>Incus vs LXC</title>
      <link>https://write.rpod.club/devops/incus-vs-lxc</link>
      <description>&lt;![CDATA[#linux #incus #lxc&#xA;&#xA;Install&#xA;&#xA;Source code: https://github.com/zabbly/incus&#xA;&#xA;Add repo key&#xA;mkdir -p /etc/apt/keyrings/&#xA;curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc&#xA;&#xA;Add remote repo&#xA;sh -c &#39;cat &lt;EOF  /etc/apt/sources.list.d/zabbly-incus-stable.sources&#xA;Enabled: yes&#xA;Types: deb&#xA;URIs: https://pkgs.zabbly.com/incus/stable&#xA;Suites: $(. /etc/os-release &amp;&amp; echo ${VERSION_CODENAME})&#xA;Components: main&#xA;Architectures: $(dpkg --print-architecture)&#xA;Signed-By: /etc/apt/keyrings/zabbly.asc&#xA;&#xA;EOF&#39;&#xA;&#xA;Install&#xA;apt-get update &amp;&amp; apt-get install incus&#xA;&#xA;Add user to incus group&#xA; sudo usermod -a -G incus USERNAME&#xA; sudo usermod -a -G incus-admin USERNAME&#xA;And re-login&#xA;&#xA;---&#xA;&#xA;First time config]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/devops/tag:linux" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">linux</span></a> <a href="/devops/tag:incus" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">incus</span></a> <a href="/devops/tag:lxc" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">lxc</span></a></p>

<h2 id="install">Install</h2>

<p>Source code: <a href="https://github.com/zabbly/incus" rel="nofollow">https://github.com/zabbly/incus</a></p>

<p>Add repo key</p>

<pre><code>mkdir -p /etc/apt/keyrings/
curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc
</code></pre>

<p>Add remote repo</p>

<pre><code>sh -c &#39;cat &lt;&lt;EOF &gt; /etc/apt/sources.list.d/zabbly-incus-stable.sources
Enabled: yes
Types: deb
URIs: https://pkgs.zabbly.com/incus/stable
Suites: $(. /etc/os-release &amp;&amp; echo ${VERSION_CODENAME})
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/zabbly.asc

EOF&#39;
</code></pre>

<p>Install</p>

<pre><code>apt-get update &amp;&amp; apt-get install incus
</code></pre>

<p>Add user to incus group</p>

<pre><code> sudo usermod -a -G incus &lt;USERNAME&gt;
 sudo usermod -a -G incus-admin &lt;USERNAME&gt;
</code></pre>

<p>And re-login</p>

<hr>

<h2 id="first-time-config">First time config</h2>
]]></content:encoded>
      <guid>https://write.rpod.club/devops/incus-vs-lxc</guid>
      <pubDate>Thu, 04 Jan 2024 10:33:06 +0000</pubDate>
    </item>
    <item>
      <title>Как копировать коммиты из одного Git repo to в другой?</title>
      <link>https://write.rpod.club/devops/how-to-copy-commits-from-one-git-repo-to-another</link>
      <description>&lt;![CDATA[Как копировать коммиты из одного Git repo to в другой?&#xA;&#xA;#git #linux&#xA;&#xA;Клонируем основное репо и заходим в него&#xA;git clone https://github.com/user1/project1&#xA;cd project1&#xA;Добавляем репо из которого нужно скопировать коммиты&#xA;git remote add oldrepo https://github.com/user1/project2&#xA;Обновляемся&#xA;git remote update&#xA;Тут можно посмотреть что и как получилось&#xA;git log --all --oneline --graph --decorate &#xA;Находим нужные коммиты и делаем cherry-pick из старого в новое репо&#xA;git cherry-pick sha-of-commit-one&#xA;git cherry-pick sha-of-commit-two&#xA;git cherry-pick sha-of-commit-three&#xA;Могут и будут возникать конфликты и их нужно будет разруливать&#xA;Проверяем что все ок&#xA;git log&#xA;И пушим в основное репо&#xA;git push origin master&#xA;Удаляем старое&#xA;git remote remove oldrepo&#xA;`]]&gt;</description>
      <content:encoded><![CDATA[<h2 id="как-копировать-коммиты-из-одного-git-repo-to-в-другой">Как копировать коммиты из одного Git repo to в другой?</h2>

<p><a href="/devops/tag:git" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">git</span></a> <a href="/devops/tag:linux" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">linux</span></a></p>

<p>Клонируем основное репо и заходим в него</p>

<pre><code>git clone https://github.com/user1/project1
cd project1
</code></pre>

<p>Добавляем репо из которого нужно скопировать коммиты</p>

<pre><code>git remote add oldrepo https://github.com/user1/project2
</code></pre>

<p>Обновляемся</p>

<pre><code>git remote update
</code></pre>

<p>Тут можно посмотреть что и как получилось</p>

<pre><code>git log --all --oneline --graph --decorate 
</code></pre>

<p>Находим нужные коммиты и делаем cherry-pick из старого в новое репо</p>

<pre><code>git cherry-pick sha-of-commit-one
git cherry-pick sha-of-commit-two
git cherry-pick sha-of-commit-three
</code></pre>

<p>Могут и будут возникать конфликты и их нужно будет разруливать
Проверяем что все ок</p>

<pre><code>git log
</code></pre>

<p>И пушим в основное репо</p>

<pre><code>git push origin master
</code></pre>

<p>Удаляем старое</p>

<pre><code>git remote remove oldrepo
</code></pre>
]]></content:encoded>
      <guid>https://write.rpod.club/devops/how-to-copy-commits-from-one-git-repo-to-another</guid>
      <pubDate>Mon, 05 Jun 2023 13:07:24 +0000</pubDate>
    </item>
    <item>
      <title>Jenkins: run shell command</title>
      <link>https://write.rpod.club/devops/prostoi-sposob-zapustit-komandu-shell-iz-jenkins</link>
      <description>&lt;![CDATA[#jenkins #shell&#xA;&#xA;простой способ запустить команду shell из master jenkins&#xA;&#xA;def proc = &#34;&#34;&#34;ls -l&#34;&#34;&#34;.execute()&#xA;proc.waitFor()&#xA;def b = new StringBuffer()&#xA;proc.consumeProcessErrorStream(b)&#xA;println proc.exitValue()&#xA;println proc.text&#xA;&#xA;` ]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/devops/tag:jenkins" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">jenkins</span></a> <a href="/devops/tag:shell" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">shell</span></a></p>

<p>простой способ запустить команду shell из master jenkins</p>

<pre><code>def proc = &#34;&#34;&#34;ls -l&#34;&#34;&#34;.execute()
proc.waitFor()
def b = new StringBuffer()
proc.consumeProcessErrorStream(b)
println proc.exitValue()
println proc.text

</code></pre>
]]></content:encoded>
      <guid>https://write.rpod.club/devops/prostoi-sposob-zapustit-komandu-shell-iz-jenkins</guid>
      <pubDate>Thu, 01 Dec 2022 06:19:37 +0000</pubDate>
    </item>
    <item>
      <title>Простая заготовка для github action</title>
      <link>https://write.rpod.club/devops/prostaia-zagotovka-dlia-github-action</link>
      <description>&lt;![CDATA[#github #action #tutorial&#xA;&#xA;Создаем файлик в репо&#xA;vim .github/workflows/demo.yml&#xA;И пишем&#xA;name: GitHub Actions&#xA;run-name: ${{ github.actor }} is testing out GitHub Actions&#xA;on: [push]&#xA;jobs:&#xA;  one:&#xA;    runs-on: ubuntu-latest&#xA;    steps:&#xA;      name: Dump GitHub context&#xA;        env:&#xA;          GITHUBCONTEXT: ${{ toJSON(github) }}&#xA;        run: echo &#34;$GITHUBCONTEXT&#34;&#xA;  changedfiles:&#xA;    runs-on: ubuntu-latest&#xA;    # Map a step output to a job output&#xA;    outputs:&#xA;      ts: ${{ steps.changes.outputs.ts }}&#xA;    steps:&#xA;        # Make sure we have some code to diff.&#xA;      name: Checkout repository&#xA;        uses: actions/checkout@v2&#xA;        with:&#xA;            fetch-depth: 0&#xA;      name: Get changed files&#xA;        id: changes&#xA;        # Set outputs using the command.&#xA;        run: |&#xA;          echo &#34;::set-output name=ts::$(git diff --name-only --diff-filter=ACMRT ${{ github.event.before }} ${{ github.sha }} | grep .swift$ | xargs)&#34;&#xA;  lint:&#xA;    runs-on: ubuntu-latest&#xA;    # require the first job to have ran&#xA;    needs: changedfiles&#xA;    # only run there are changed files&#xA;    if: ${{needs.changedfiles.outputs.ts}}&#xA;    steps:&#xA;      name: echo changed files&#xA;        run: echo ${{needs.changedfiles.outputs.ts}}&#xA;&#xA;Тут job &#34;one&#34; - просто распечатывает весь json data event, который приходит из Gighub&#xA;&#xA;А вот &#34;lint&#34; запустится только тогда, когда в будет изменен любой файл с расширением .swift (grep .swift$)&#xA;&#xA;---&#xA;&#xA;PS: Нужно помнить что &#xA;PS1: `github.event.before работает только для push, для pullrequest нужно использовать github.event.pull_request.base.sha`]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/devops/tag:github" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">github</span></a> <a href="/devops/tag:action" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">action</span></a> <a href="/devops/tag:tutorial" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">tutorial</span></a></p>

<p>Создаем файлик в репо</p>

<pre><code>vim .github/workflows/demo.yml
</code></pre>

<p>И пишем</p>

<pre><code>name: GitHub Actions
run-name: ${{ github.actor }} is testing out GitHub Actions
on: [push]
jobs:
  one:
    runs-on: ubuntu-latest
    steps:
      - name: Dump GitHub context
        env:
          GITHUB_CONTEXT: ${{ toJSON(github) }}
        run: echo &#34;$GITHUB_CONTEXT&#34;
  changedfiles:
    runs-on: ubuntu-latest
    # Map a step output to a job output
    outputs:
      ts: ${{ steps.changes.outputs.ts }}
    steps:
        # Make sure we have some code to diff.
      - name: Checkout repository
        uses: actions/checkout@v2
        with:
            fetch-depth: 0
      - name: Get changed files
        id: changes
        # Set outputs using the command.
        run: |
          echo &#34;::set-output name=ts::$(git diff --name-only --diff-filter=ACMRT ${{ github.event.before }} ${{ github.sha }} | grep .swift$ | xargs)&#34;
  lint:
    runs-on: ubuntu-latest
    # require the first job to have ran
    needs: changedfiles
    # only run there are changed files
    if: ${{needs.changedfiles.outputs.ts}}
    steps:
      - name: echo changed files
        run: echo ${{needs.changedfiles.outputs.ts}}
</code></pre>

<p>Тут job “one” – просто распечатывает весь json data event, который приходит из Gighub</p>

<p>А вот “lint” запустится только тогда, когда в будет изменен любой файл с расширением .swift (grep .swift$)</p>

<hr>

<p>PS: <em>Нужно помнить что <code>::set-output name=ts::</code> скоро будет отключен</em></p>

<p>PS1: <code>github.event.before работает только для push, для pull_request нужно использовать github.event.pull_request.base.sha</code></p>
]]></content:encoded>
      <guid>https://write.rpod.club/devops/prostaia-zagotovka-dlia-github-action</guid>
      <pubDate>Tue, 25 Oct 2022 11:45:57 +0000</pubDate>
    </item>
    <item>
      <title>Сборка ядра в Debian 10</title>
      <link>https://write.rpod.club/devops/sborka-iadra-v-debian-10</link>
      <description>&lt;![CDATA[#kernel #debian #source #build&#xA;&#xA;Одно из главных особеностей Debian это очень долгое время обновления. Весь мир уже перешел на Python 3.10, а в официальных репо Debian до сих пор 3.7.&#xA;И это касается всех частей системы.&#xA;&#xA;И вот появился клиент, который очень давно не обновлялся и даже  не планирует этого делать. И он использует очень старое ядро (по современным меркам). Поэтому для того чтобы дать полную поддержку этому клиенту необходимо полностью симулировать его конфигурацию. &#xA;&#xA;Процесс довольно простой, но очень нудный и долгий.&#xA;&#xA;1. Установка зависимостей&#xA;&#xA;apt-get install gpg gcc make git exuberant-ctags bc libssl-dev libelf-dev elfutils fakeroot build-essential zlib1g-dev libncurses5-dev bison flex&#xA;&#xA;2. Добавляем подпись&#xA;gpg --locate-keys torvalds@kernel.org gregkh@kernel.org&#xA;&#xA;3. Скачиваем исходники&#xA;wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.143.tar.gz&#xA;wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.143.tar.sign&#xA;wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.143.tar.xz&#xA;&#xA;4. Распаковываем&#xA;tar xzf linux-5.4.143.tar.gz&#xA;mv linux-5.4.143 /usr/src/&#xA;mv /usr/src/linux-5.4.143 /usr/src/linux-source-5.4.143-amd64&#xA;&#xA;5. Копируем конфиг&#xA;cp /boot/config-$(uname -r) /usr/src/linux-source-5.4.143-amd64/.config&#xA;&#xA;6. Изменяем конфигурацию&#xA;make menuconfig&#xA;не забудем сохранить  нужно еще изменить /usr/src/linux-source-5.4.143-amd64/Makefile&#xA;EXTRAVERSION = -amd64&#xA;&#xA;7. Стартуем&#xA;make deb-pkg &#xA;&#xA;После завершения получим 4 файла:&#xA;linux-headers-5.4.143-amd645.4.143-amd64-1amd64.deb&#xA;linux-image-5.4.143-amd645.4.143-amd64-1amd64.deb&#xA;linux-image-5.4.143-amd64-dbg5.4.143-amd64-1amd64.deb&#xA;linux-libc-dev5.4.143-amd64-1amd64.deb&#xA;&#xA;тут есть в картинках&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/devops/tag:kernel" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">kernel</span></a> <a href="/devops/tag:debian" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">debian</span></a> <a href="/devops/tag:source" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">source</span></a> <a href="/devops/tag:build" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">build</span></a></p>

<p>Одно из главных особеностей Debian это очень долгое время обновления. Весь мир уже перешел на Python 3.10, а в официальных репо Debian до сих пор 3.7.
И это касается всех частей системы.</p>

<p>И вот появился клиент, который очень давно не обновлялся и даже  не планирует этого делать. И он использует очень старое ядро (по современным меркам). Поэтому для того чтобы дать полную поддержку этому клиенту необходимо полностью симулировать его конфигурацию.</p>

<p>Процесс довольно простой, но очень нудный и долгий.</p>

<h3 id="1-установка-зависимостей">1. Установка зависимостей</h3>

<pre><code>apt-get install gpg gcc make git exuberant-ctags bc libssl-dev libelf-dev elfutils fakeroot build-essential zlib1g-dev libncurses5-dev bison flex
</code></pre>

<h3 id="2-добавляем-подпись">2. Добавляем подпись</h3>

<pre><code>gpg --locate-keys torvalds@kernel.org gregkh@kernel.org
</code></pre>

<h3 id="3-скачиваем-исходники">3. Скачиваем исходники</h3>

<pre><code>wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.143.tar.gz
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.143.tar.sign
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.143.tar.xz
</code></pre>

<h3 id="4-распаковываем">4. Распаковываем</h3>

<pre><code>tar xzf linux-5.4.143.tar.gz
mv linux-5.4.143 /usr/src/
mv /usr/src/linux-5.4.143 /usr/src/linux-source-5.4.143-amd64
</code></pre>

<h3 id="5-копируем-конфиг">5. Копируем конфиг</h3>

<pre><code>cp /boot/config-$(uname -r) /usr/src/linux-source-5.4.143-amd64/.config
</code></pre>

<h3 id="6-изменяем-конфигурацию">6. Изменяем конфигурацию</h3>

<pre><code>make menuconfig
</code></pre>

<p>не забудем сохранить  нужно еще изменить /usr/src/linux-source-5.4.143-amd64/Makefile</p>

<pre><code>EXTRAVERSION = -amd64
</code></pre>

<h3 id="7-стартуем">7. Стартуем</h3>

<pre><code>make deb-pkg 
</code></pre>

<p>После завершения получим 4 файла:
– linux-headers-5.4.143-amd64<em>5.4.143-amd64-1</em>amd64.deb
– linux-image-5.4.143-amd64<em>5.4.143-amd64-1</em>amd64.deb
– linux-image-5.4.143-amd64-dbg<em>5.4.143-amd64-1</em>amd64.deb
– linux-libc-dev<em>5.4.143-amd64-1</em>amd64.deb</p>

<p><a href="http://blog.smansoft.com/2021/06/20/building-linux-kernel-on-the-debian-os-debian-10-buster/" rel="nofollow">тут есть в картинках</a></p>
]]></content:encoded>
      <guid>https://write.rpod.club/devops/sborka-iadra-v-debian-10</guid>
      <pubDate>Wed, 14 Sep 2022 09:05:10 +0000</pubDate>
    </item>
    <item>
      <title>Неявные сокращения в bash</title>
      <link>https://write.rpod.club/devops/neiavnye-sokrashcheniia-v-bash</link>
      <description>&lt;![CDATA[bash&#xA;&#xA;&amp;&#x9;Run the previous command in the background&#x9;ls &amp;&#xA;&amp;&amp;&#x9;Logical AND&#x9;if [ &#34;$foo&#34; -ge &#34;0&#34; ] &amp;&amp; [ &#34;$foo&#34; -le &#34;9&#34;]&#xA;||&#x9;Logical OR&#x9;if [ &#34;$foo&#34; -lt &#34;0&#34; ] || [ &#34;$foo&#34; -gt &#34;9&#34; ]&#xA;^&#x9;Start of line&#x9;grep &#34;^foo&#34;&#xA;$&#x9;End of line&#x9;grep &#34;foo$&#34;&#xA;=&#x9;String equality (cf. -eq)&#x9;if [ &#34;$foo&#34; = &#34;bar&#34; ]&#xA;!&#x9;Logical NOT&#x9;if [ &#34;$foo&#34; != &#34;bar&#34; ]&#xA;$$&#x9;PID of current shell&#x9;echo &#34;my PID = $$&#34;&#xA;$!&#x9;PID of last background command&#x9;ls &amp; echo &#34;PID of ls = $!&#34;&#xA;$?&#x9;exit status of last command&#x9;ls ; echo &#34;ls returned code $?&#34;&#xA;$0&#x9;Name of current command (as called)&#x9;echo &#34;I am $0&#34;&#xA;$1&#x9;Name of current command&#39;s first parameter&#x9;echo &#34;My first argument is $1&#34;&#xA;$9&#x9;Name of current command&#39;s ninth parameter&#x9;echo &#34;My ninth argument is $9&#34;&#xA;$@&#x9;All of current command&#39;s parameters (preserving whitespace and quoting)&#x9;echo &#34;My arguments are $@&#34;&#xA;$&#x9;All of current command&#39;s parameters (not preserving whitespace and quoting)&#x9;echo &#34;My arguments are $&#34;&#xA;-eq&#x9;Numeric Equality&#x9;if [ &#34;$foo&#34; -eq &#34;9&#34; ]&#xA;-ne&#x9;Numeric Inquality&#x9;if [ &#34;$foo&#34; -ne &#34;9&#34; ]&#xA;-lt&#x9;Less Than&#x9;if [ &#34;$foo&#34; -lt &#34;9&#34; ]&#xA;-le&#x9;Less Than or Equal&#x9;if [ &#34;$foo&#34; -le &#34;9&#34; ]&#xA;-gt&#x9;Greater Than&#x9;if [ &#34;$foo&#34; -gt &#34;9&#34; ]&#xA;-ge&#x9;Greater Than or Equal&#x9;if [ &#34;$foo&#34; -ge &#34;9&#34; ]&#xA;-z&#x9;String is zero length&#x9;if [ -z &#34;$foo&#34; ]&#xA;-n&#x9;String is not zero length&#x9;if [ -n &#34;$foo&#34; ]&#xA;-nt&#x9;Newer Than&#x9;if [ &#34;$file1&#34; -nt &#34;$file2&#34; ]&#xA;-d&#x9;Is a Directory&#x9;if [ -d /bin ]&#xA;-f&#x9;Is a File&#x9;if [ -f /bin/ls ]&#xA;-r&#x9;Is a readable file&#x9;if [ -r /bin/ls ]&#xA;-w&#x9;Is a writable file&#x9;if [ -w /bin/ls ]&#xA;-x&#x9;Is an executable file&#x9;if [ -x /bin/ls ]&#xA;( ... )&#x9;Function definition&#x9;function myfunc() { echo hello }]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/devops/tag:bash" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">bash</span></a></p>

<p>&amp;   Run the previous command in the background  ls &amp;
&amp;&amp;  Logical AND if [ “$foo” -ge “0” ] &amp;&amp; [ “$foo” -le “9”]
||  Logical OR  if [ “$foo” -lt “0” ] || [ “$foo” -gt “9” ]
^   Start of line   grep “^foo”
$   End of line grep “foo$”
=   String equality (cf. -eq)   if [ “$foo” = “bar” ]
!   Logical NOT if [ “$foo” != “bar” ]
$$  PID of current shell    echo “my PID = $$”
$!  PID of last background command  ls &amp; echo “PID of ls = $!”
$?  exit status of last command ls ; echo “ls returned code $?”
$0  Name of current command (as called) echo “I am $0”
$1  Name of current command&#39;s first parameter   echo “My first argument is $1”
$9  Name of current command&#39;s ninth parameter   echo “My ninth argument is $9”
$@  All of current command&#39;s parameters (preserving whitespace and quoting) echo “My arguments are $@”
$*  All of current command&#39;s parameters (not preserving whitespace and quoting) echo “My arguments are $*”
-eq Numeric Equality    if [ “$foo” -eq “9” ]
-ne Numeric Inquality   if [ “$foo” -ne “9” ]
-lt Less Than   if [ “$foo” -lt “9” ]
-le Less Than or Equal  if [ “$foo” -le “9” ]
-gt Greater Than    if [ “$foo” -gt “9” ]
-ge Greater Than or Equal   if [ “$foo” -ge “9” ]
-z  String is zero length   if [ -z “$foo” ]
-n  String is not zero length   if [ -n “$foo” ]
-nt Newer Than  if [ “$file1” -nt “$file2” ]
-d  Is a Directory  if [ -d /bin ]
-f  Is a File   if [ -f /bin/ls ]
-r  Is a readable file  if [ -r /bin/ls ]
-w  Is a writable file  if [ -w /bin/ls ]
-x  Is an executable file   if [ -x /bin/ls ]
( ... ) Function definition function myfunc() { echo hello }</p>
]]></content:encoded>
      <guid>https://write.rpod.club/devops/neiavnye-sokrashcheniia-v-bash</guid>
      <pubDate>Thu, 11 Aug 2022 06:30:10 +0000</pubDate>
    </item>
    <item>
      <title>JFROG странное</title>
      <link>https://write.rpod.club/devops/jfrog-strannoe</link>
      <description>&lt;![CDATA[#issue #jfrog&#xA;&#xA;Обнаружил странное поведение своего artifactory server jfrog в связке с AD.&#xA;Когда у меня, согласно политике, истек пароль и я его успешно сменил, я вдруг не смог сделать простую вещь в консоле:&#xA;&#xA;  docker login artifactory.server&#xA;Login did not succeed, error: Error response from daemon: Get &#34;https://artifactory.server/v2/&#34;: unknown: Bad credentials&#xA;Username (****):&#xA;Password:&#xA;Error response from daemon: Get &#34;https://artifactory.server/v2/&#34;: unknown: This request is blocked due to recurrent login failures, please try again in 53 seconds&#xA;Упс, сказал я и открыл тикет в суппорте.&#xA;&#xA;Но все же решил проверить и зайти через вебморду jfrog. &#xA;И тут ждал второй сюрприз. Войти удалось без проблем! &#xA;И docker login заработал тоже!&#xA;&#xA;И так вопрос - что это было?&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/devops/tag:issue" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">issue</span></a> <a href="/devops/tag:jfrog" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">jfrog</span></a></p>

<p>Обнаружил странное поведение своего artifactory server jfrog в связке с AD.
Когда у меня, согласно политике, истек пароль и я его успешно сменил, я вдруг не смог сделать простую вещь в консоле:</p>

<pre><code>&gt; docker login artifactory.server
Login did not succeed, error: Error response from daemon: Get &#34;https://artifactory.server/v2/&#34;: unknown: Bad credentials
Username (****):
Password:
Error response from daemon: Get &#34;https://artifactory.server/v2/&#34;: unknown: This request is blocked due to recurrent login failures, please try again in 53 seconds
</code></pre>

<p>Упс, сказал я и открыл тикет в суппорте.</p>

<p>Но все же решил проверить и зайти через вебморду jfrog.
И тут ждал второй сюрприз. Войти удалось без проблем!
И <em>docker login</em> заработал тоже!</p>

<p>И так вопрос – что это было?</p>
]]></content:encoded>
      <guid>https://write.rpod.club/devops/jfrog-strannoe</guid>
      <pubDate>Wed, 10 Aug 2022 06:18:02 +0000</pubDate>
    </item>
  </channel>
</rss>