Обработка ошибок в Swift

Зачем все это нужно?

Обработка ошибок во время выполнения программы и самое главное — реакция приложения на эти ошибки — это хорошая практика для любого программного продукта. Согласитесь, если вы пользуетесь приложением и в какой-то момент оно без каких либо видимых проявлений «падает»,  — что вы подумаете  про разработчика? Думается — ничего хорошего. А ведь можно в коде добавить обработчики ошибок, и в читабельном формате сообщать пользователю что именно пошло не так. С одной стороны — это значительно повышает юзабилити приложения, с другой облегчает отладку при разработке.

Однако, ошибки не всегда нужно обрабатывать. Языковые функции  Swift позволяют избежать некоторых ошибок в целом. Как правило, если вы можете избежать возникновения ошибки, возьмите именно этот путь проектирования и разработки мобильного приложения iOS. Но если вы не можете избежать потенциального условия появления ошибки, то явная обработка — лучший вариант.

 

В этой статье я составлю памятку по работе с ошибками на языке программирования  Swift 3

Избегаем ошибок nil

Так как Swift обладает элегантными опциональными возможностями обработки, вы можете полностью избежать  ошибки при попытке извлечь значение из опционала, когда значение в этом самом опционале не представлено.

Быстрый пример — получение элемента из словаря по ключу возвращает опциональное значение. Т.е. если в словаре есть запрошенный ключ — то вернется значение, а если нет — то вернется nil. 

Как умный программист, вы можете манипулировать этой функцией, чтобы преднамеренно перевести nil в состояние ошибки. Такой подход выгоден в случае если нужно не вызвать падение программы и при этом ничего не делать если ошибка возникла.

ва типичных примера исключения ошибок Swift с использованием nil — это проваливающиеся инициализаторы и инструкция guard.

Проваливающиеся инициализаторы

Проваливающиеся инициализаторы предотвращают создание объекта, если для их создания не предоставлена ​​достаточная информация. До появления этих инициализаторов в Swift, да и в других языках программирования этот функционал обычно достигался с помощью шаблона Factory Method. Old-school пример создания станции технического обслуживания (СТО)  выглядит как метод класса:

//
//  proSwift.ru
//
//  Swift 3

import Foundation


enum MotorOil: String {
    case valvoline = "Valvoline"
    case mobil = "Mobil"
    case castrol = "Castrol"
    case shell = "Shell"
}

struct Stantion {
    var oilBrand: MotorOil = .valvoline
    
    
    static func create(with motorOliBrand: String) -> Stantion? {
        if let oil = MotorOil(rawValue: motorOliBrand) { // проверяем если ли масляный бренд, который соответсвует строке, переданной в функцию
            var stantion = Stantion()
            stantion.oilBrand = oil
            return stantion
        } else {
            return nil
        }
    }
}

let audiStation = Stantion.create(with: "Castrol")
let kiaStation = Stantion.create(with: "Zic")

Две последних строки нам показывают следующее — станция Audi была создана, т.к. в наших брендах масел мы смоги найти Castrol, а вот  kiaStation будет равна nil, т.к. попытка найти масло Zic обернулось неудачей.

То же метод создания мы можем написать при помощи проваливающегося инициализатора.  Давайте ЗАМЕНИМ метод create(motorOilBrand:) на код инициализатора:

//
//  proSwift.ru
//
//  Swift 3
    
    init?(brand: String) {
        if let oil = MotorOil(rawValue: brand) {
            self.oilBrand = oil
        } else {
            return nil
        }
    }

Также следует изменить строки создания станций.

 //
 //  proSwift.ru
 //
 //  Swift 3

//let audiStation = Stantion.create(with: "Castrol")
//let kiaStation = Stantion.create(with: "Zic")

let audiStation = Stantion(brand: "Castrol")
let kiaStation = Stantion(brand: "Zic")

То же самое: Ауди — есть объект, Киа — nil.

 Инструкция guard

guard — это быстрый способ утверждать, что что-то истинно. Например, если значение > 0 или если условное выражение может быть развернуто то нужно что-то сделать (выполнить код). Затем вы можете выполнить блок кода, если проверка завершилась неудачей.

Без долгих рассуждений — сразу к примеру: изменим наш инициализатор при помощи инструкции guard

//
//  proSwift.ru
//
//  Swift 3

import Foundation


enum MotorOil: String {
    case valvoline = "Valvoline"
    case mobil = "Mobil"
    case castrol = "Castrol"
    case shell = "Shell"
}

struct Stantion {
    var oilBrand: MotorOil = .valvoline
    
    init?(brand: String) {
        guard let oil = MotorOil(rawValue: brand) else { return nil }

        self.oilBrand = oil

    }
}

let audiStation = Stantion(brand: "Castrol")
let kiaStation = Stantion(brand: "Zic")

При этом изменении нет необходимости в переносе else на отдельную строку, и случай сбоя становится более очевидным, так как он теперь находится наверху инициализатора. Более того, код стал более читабелниый и понятный с точки зрения направления чтения (сверху вниз): то что задумывалось разработчиком и должно произойти, если не возникнут ошибки, — описано по ходу текста программы.

Устранение ошибок с помощью пользовательской обработки

Примерчик немного кривоват, но суть от этого не меняется

//: Playground - noun: a place where people can play
//
//  proSwift.ru
//
//  Swift 3

import Foundation


enum MotorOil: String {
    case valvoline = "Valvoline"
    case mobil = "Mobil"
    case castrol = "Castrol"
    case shell = "Shell"
    case unknown
}

struct Stantion {
    var oilBrand: MotorOil = .valvoline
    
    init?(brand: String) {
        guard let oil = MotorOil(rawValue: brand) else { return nil }
        self.oilBrand = oil
    }
    
    init?(motorOil: MotorOil) {
        self.oilBrand = motorOil
    }
}

let audiStation = Stantion(brand: "Castrol")
let kiaStation = Stantion(brand: "Zic")


protocol Duty {
    var boxSpec: Bool? { get }
    var job: Stantion? { get set }
    
    func isOnDuty() -> Bool
}

enum CarGearBoxTypes {
    case automatic
    case manual
}


struct Employee: Duty {
    var name: String
    
    
    var boxSpec: Bool?
    var job: Stantion?
    
    var gearBox: CarGearBoxTypes?
    
    func isOnDuty() -> Bool {
        guard let job = self.job else { return false }
        return true
    }
    
    
    func fixManualGearBoxAndReturnMotorOil() -> MotorOil {
        if let job = job { // у работника есть ссылка на работу?
            if isOnDuty() { // работник вообще принят на работу?
                if let spec = boxSpec { // есть ли опыт работы с коробками
                    if let gearBox = gearBox { // может машину уже разобрали и коробку сняли
                        if gearBox == .manual { // если коробка мехарническая
                            return job.oilBrand
                        }
                    }
                }
            }
        }
        return .unknown
    }
}

У нас есть работник станции техобслуживания, который приписан к станции (job), нанят на работу (isOnDuty), имеет или не имеет опыт работы с трансмиссией (boxSpec) и автомобиль с коробкой или без нее пока неизвестного типа. Соответсвенно, чтобы выполнить работу по починке механической коробки передач и выдать бренд масла, которое было использовано при ремонте нужно сделать следующее:

  • Уточнить если ли вообще у работника ссылка его работу?
  • Выяснить работник вообще принят на работу?
  • Понять есть ли опыт работы с коробками у работника
  • Удостовериться, что коробка еще на месте и ее можно ремонтировать
  • Быть уверенным, что коробка механическая а не автоматическая

1 Comment on “Обработка ошибок в Swift

  1. Спасибо за хорошую и понятную статью. Могли бы вы так же написать про enums, struct. Про то, где их использовать, какая между ними разница и тд.

Добавить комментарий для Swifty Отменить ответ

Ваш адрес email не будет опубликован.

*

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.