Три ошибки iOS-разработчика, которые могут дорого стоить


     
    Создание iOS-приложения – непростая задача. Разработчикам хочется как можно быстрее завершить этот процесс и наконец запуститься в AppStore. Но на этом все не заканчивается: впереди у создателей долгие годы исправления ошибок, улучшения функций и совместной работы с другими разработчиками. Мы бы хотели немного облегчить им жизнь и для этого решили разобрать три вещи, которые нужно избегать при iOS-разработке (спасибо Envato Tuts+ за информацию).

    1. Константы, константы… мне и с переменными норм


    На первый взгляд, переменные более универсальны чем константы. Но если вы можете использовать константу, то лучше к переменным не прибегать. У констант есть несколько преимуществ.

    Читаемость


    Это, пожалуй, главное преимущество. Предположим, вы пишете e-commerce приложение для США. Указываете локальную ставку налога с продаж — 8,75%. И в этот момент к разработке подключается условный Вася, который о налоге с продаж ничего не знает.

    Если вы введете переменную для обозначения ставки налога с продаж, он может изменить ее буквально одной строкой кода, что может серьезно навредить приложению. Если вы поменяете ключевое слово с var на let, компилятор сообщит, что это значение не может быть изменено, и Вася отдернет свои судьбоносные персты от клавиатуры.

    Ссылка на значение


    С помощью let можно ссылаться на какое-либо значение в коде. Создаете вы приложение, создаете и вдруг по примеру Rolling Stones решаете покрасить все в черный. Если вы прописали константу, то при изменении в одном месте все остальные ссылки на цвет тоже изменятся. А если нет, то придется выискивать и менять все вручную.

    Инстанцирование класса или структуры


    Если вы создаете синглтон (singleton), вам нужно при этом создать и общий экземпляр (shared instance) класса. Обычно это делается через объявление static let внутри объявления класса. После этого вы даете имя константе, присваиваете ее экземпляру класса и можете спокойно использовать его во всем приложении.

    Если вам нужно создать экземпляр обычного класса (скажем, в ViewController.swift), вы создаете константу и присваиваете ее экземпляру нужного вам класса – таким образом возникает ссылка, которой вы можете легко воспользоваться во всем файле. Константы снова в деле!

    Как видите, константы имеют много преимуществ. Они повышают читаемость кода, они полезны для хранения неизменяемых величин и, конечно, не так бесполезны, как вы думаете. Крутые разработчики любят константы и изменяют им с переменными только если от изменения значений никуда не деться.



    2. Bang Operator! Как круто звучит, то что надо для опционала!


    Опционалы — очень мощная фича Swift. Это типы а-ля int и String, аннотированные с вопросительным знаком после объявления типа. Если вы хотите объявить переменную как опциональную строку, то можете просто написать:

    var someVariable: String?

    Это сигнал компилятору, что в ней либо будет значение, либо не будет. String? и String считаются двумя разными типами.

    Опционал — это подарочная коробка. В этой коробке может храниться, а может и не храниться значение. Если вы хотите это узнать, нужно опционал развернуть. Это делается по-разному.
     

    Неправильно: Forced Unwrapping (Принудительное извлечение значения)


    Эта операция (выполняется с помощью восклицательного знака) называется Bang Operator. Не советуем ей пользоваться. Если в значении извлекаемого таким образом опционала будет nil (ничто), то приложение рухнет. Вот например как здесь:

    var someVariable: String?
    var somethingElse: String = "hello"
     
    func setupApp() {
        self.somethingElse = self.someVariable!
    }
    

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

    Правильно: Optional Binding (Привязка опционала)


    Это один из самых популярных методов работы с опционалами. Вы просто присваиваете значение опционала константе через инструкцию if. Если из опционала можно извлечь значение, компилятор входит в замыкание, и вы можете использовать созданную константу. В противном случае вы попадаете на ветку else и делаете вид, что ничего не произошло.

    Для сравнения возьмем тот же пример:

    var someVariable: String?
    var somethingElse: String = "hello"
     
    func setupApp() {
        if let theThing = someVariable {
            self.somethingElse = self.someVariable!
        } else {
            print("error")
        }
    }
     

    С привязкой опционала компилятор не рушит все приложение, а спокойно входит в ветку else и печатает «ошибку».

    И снова правильно: Optional Chaining (опциональная цепочка)


    Другим распространенным способом безопасного извлечения опционалов является опциональная цепочка. Так вы сможете полностью избежать значений nil с помощью всего одной строки. Если в какой-то момент такое значение будет получаться, эта строка кода просто перестает выполняться. Например:

    var someClass: SomeClass? = SomeClass()
    var somethingElse: String?
     
    func setupApp() {
        self.somethingElse = someClass?.createString()
    }

    Если someClass равен nil, то не будет выполнена вся строка, значение somethingElse становится равным nil. Если значение отлично от nil, как в примере выше, то оно присваивается переменной somethingElse. В любом случае приложение не упадет.
     

    И опять верно: Nil Coalescing (Оператор объединения по nil)


    Этот метод позволяет обрабатывать опционалы одной строкой, но в отличие от метода выше, вы обязаны указываете значение по умолчанию или «else». То есть для сценария, когда опционал окажется равным nil). Например:

    var someVariable: String?
    var somethingElse: String = "hello"
     
    func setupApp() {
        self.somethingElse = someVariable ?? "error"
    }

     
    Выглядит загадочно, но суть проста. Если выражение слева имеет какое-либо значение (то есть не равное nil), то это значение и будет использовано. Если значение равно nil, то будет использовано значение по умолчанию – в этом случае, захардкоженная строка. Выражение справа должна быть отлично от nil, а его тип должен быть неопциональным — иначе теряется весь смысл.

    3. Оставим все эти шаблоны архитектуры на потом


    И напоследок классическая ошибка. Многие не структурируют код, отчего страдает устойчивость, эффективность и способность его поддерживать в принципе. Можно втиснуть весь свой код в классы ViewController, но этот опрометчивый шаг отольется горючими слезами, когда придется изменить и дебажить код.



    Код — это фундамент, на котором вы разрабатываете свое приложение. Без последовательно заложенного фундамента многоэтажный дом вашего приложения легко обрушится. Фундамент iOS приложения – это выбранный шаблон проектирования. Рассмотрим два наиболее часто используемых шаблона.

    MVC (Model-View-Controller)


    Шаблон проектирования Model-View-Controller, или MVC, разделяет каждую часть вашего кода на три части: модель, вид и контроллер.

    • Модель. Это по сути данные приложения. Эта часть охватывает многократно используемые структуры и классы, которые работают только с данными приложения. Модель не работает ни с чем, что связано с view или с тем, как информация будет показана пользователю.
    • Вид. Отвечает только за визуальное представление данных, а также за взаимодействие с пользователем. Он не обрабатывает ничего, что связано с данными или с конкретными view. Это просто  класс, который можно многократно использовать без повторения кода.
    • Контроллер. Вот наш главный герой. Он берет данные из модели, а затем отправляет их во view, чтобы отобразить их для пользователя. Обычно это происходит в ViewController.swift – он воспринимает входные данные и меняет модель по мере необходимости.

    Существует множество вариаций MVC, например, MVVM и MVP. О них стоит почитать, но принцип их работы схож с MVC, поэтому мы не будем здесь на них останавливаться. Все это — шаблоны проектирования, которые делают наш код модульным.

    Синглтоны


    Синглтон — это единственный экземпляр класса, который всегда присутствует в памяти. И что в нем особенного? Предположим, вы создаете приложение, которое подключается к базе данных, и вам нужно куда-то разместить все подключения. Синглтоны для этого подойдут идеально. Вот как их создавать:

    // Declaration
    class DataService {
        static var shared = DataService()
         
        func createUser() {
        }
    }
     
    // Call-site
    DataService.shared.createUser()
     

    Если следовать этим простым советам, приложение будет легче поддерживать, и вы найдете ошибки быстрее клиентов.

    Все рассмотренные нами ошибки прекрасно иллюстрируют старинную русскую мудрость: «Тише едешь — дальше будешь». Надеемся, что статья была полезна, и ждем, что вы тоже расскажете нам, как обходили какие-нибудь подводные камни iOS-разработки. Обещаем приятный подарок от Программы ЕФС автору самого оригинального рассказа!
    Поделиться публикацией
    Комментарии 22
    • –3

      Константы лучше переменных? Даже безотносительно детсадовской постановки вопроса «мама лучше папы» — человеческий подход к проблеме «хочу везде поменять цвет» называется dependency injection.

      • +4
        Синглтон — это единственный экземпляр класса, который всегда присутствует в памяти. И что в нем особенного? Предположим, вы создаете приложение, которое подключается к базе данных, и вам нужно куда-то разместить все подключения. Синглтоны для этого подойдут идеально.

        Di для этого подойдут идеально
      • +3
        Ошибка в примере:
        func setupApp() {
            if let theThing = someVariable {
                self.somethingElse = self.someVariable!
            } else {
                print("error")
            }
        }

        Хотя должно быть:
        func setupApp() {
            if let theThing = someVariable {
                self.somethingElse = theThing // Значение уже развернуто в theThing
            } else {
                print("error")
            }
        }


        Плюсом, нельзя забывать о такой конструкции:
        guard let theThing = someVariable else { return } // В блоке else обязательно должен быть шаг вперёд(например continue) или return.
        theThing.doSomething()
        • +4
          Существует множество вариаций MVC, например, MVVM и MVP. О них стоит почитать, но принцип их работы схож с MVC

          Не совсем. MVC — архитектурный паттерн, два других, которые вы назвали, — это не вариации, а два других архитектурных паттерна. Лучше бы написать как-то так: «существуют и другие архитектурные шаблоны проектирования, такие, как MVP, MVVM, VIPER, но их рассмотрение выходит за рамки данной статьи». Принципы у них ни разу не схожи.
          • +2
            Вы забыли указать, что опционалы — это ни что иное, как синтаксический сахар, внутри которого скрываются простые генерик перечисления со значениями .none и .some(T). Соответственно, не возбраняется их разворачивать switch/case'ом.

            Второе, в вашем синглтоне не скрыт инициализатор и ничто не помешает вызвать его
            let service = DataService()


            Так же стоит заметить, что Nil Coalescing работает медленно, и использовать его в больших количествах и в сложных выражениях стоит аккуратнее, иначе сборка может затянутся или вообще компилятор откажется собирать проект с сообщением:
            Expression was too complex to be solved in reasonable time
            • +1
              Я не iOS программист. После прочтения поста возникает вопрос.
              iOS только приходит к нормальной разработке через общепринятые паттерны, или автор пишет про широко известные вещи?
              Я думаю, что, скорее, второе. Если так, то он может относиться к любому языку и звучать как «ешь твердое вилкой, а жидкое ложкой»
              • +2

                В синглтоне тоже не все гладко.
                Правильный вот:


                class Singleton {
                
                    static let shared = Singleton()
                
                    private init() {}
                
                }
                • –1
                  Зачем писать init, если он есть по умолчанию?
                  • +1
                    Потому что здесь у него модификатор доступа private. У этого короткого кода есть вполне конкретный смысл. Почитайте про модификаторы.
                  • 0
                    Более правильно будет

                    final class Singleton {
                    ...
                    }
                    • 0

                      Ну такое. А если нужно этот синглтон "замокать" в юнит-тестах?

                    • –1

                      Правильный синглтон — отсутствующий синглтон.
                      Ибо такой синглтон, практически всегда, есть неявная зависимость.

                    • 0
                      а в офф. доках разве не тоже самое?)
                      • +1
                        Обещаем приятный подарок от Программы ЕФС автору самого оригинального рассказа!

                        Высылайте подарок Саше.
                        Cписок историй:
                        Short fairytales with unhappy endings.
                        • 0
                          Спасибо за ссылку, приглашайте Александра в диалог, чтобы он мог поделиться кейсами и историями в комментариях.
                          • 0

                            Божественный комментарий.

                          • +2
                            1. Так чем константы лучше переменных? Был приведен способ использования констант — в качестве глобальных значений, но это же не преимущество по сравнению с переменными. Переменная тоже это может. Преимущества то в другом — в том что константы — это неизменяемые значения, это значит что: компилятор может проводить с ними определенные оптимизации; они потокобезопасны (так как доступны только на чтение).
                            2. Не всегда force unwrap это ошибка. Иногда это нужно, например, для того, чтобы указать, что переменная не может быть nil, но установить значение сразу мы ей не можем (пример — outlets). Когда точно известно, что значение не может быть nil (пример — UIImage(named: «img»)! — так даже легче не забыть добавить ресурс).

                            Слово singleton встречается в тексте подозрительно часто, это гипноз?! Сейчас прочитает это какой-нибудь начинающий разработчик и начнет синглтоны клепать где попало. Вы же наверняка знаете проблемы синглтонов, напишите — будет полезно.
                            • 0
                              По пунктам 1 и 2 — согласен, Вы правы.

                              По поводу синглтонов. Специально для начинающих разработчиков — не надо клепать синглтоны где попало :) Бездумное использование глобальных переменных (пусть даже обернутых в синглтон) не есть признак хорошей архитектуры, часто без него можно обойтись (например, с помощью dependency injection, как справедливо заметили здесь). Из минусов синглтонов я бы выделил сложность их тестирования. Модульное тестирование предполагает независимость тестов друг от друга (т.е. по-хорошему их можно запускать в любой очередности). Если какой-то тест изменяет некоторую переменную в синглтоне, а другой тест использует ее значение, то получаем зависимость между тестами.
                              Более подробно про проблемы синглтонов в статье: www.objc.io/issues/13-architecture/singletons
                          • –1

                            Чем дальше, тем больше swift напоминает Delphi. Вот и до констант уже добрались, и синглтоны полюбили )

                            • +1
                              Я вообще считаю, что сколько людей — столько и версий синглтона.

                              (с) книга «Хрестоматия iOS паттернов. На всякий»

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое