Я начал изучать swift сразу же после анонса. За первые пару дней я прочитал The Swift Programming Language(Apple). Я писал небольшие приложения и одновременно игрался с новым языком, но никогда не чувствовал, что язык достаточно взрослый для больших проектов.
Но это было до анонса swift 2.0. После анонса я стал использовать swift все больше и больше и наконец недавно начал писать большой проект для The App Business используя swift.
Я решил поделиться своими наблюдениями - некоторые из них очевидны и общеприняты, но, надеюсь, некоторые будут новыми для большинства читателей.
Предпочитайте let
вместо var
Тренируйте свой мозг и рефлексы по умолчанию использовать let. Вы можете объявить переменную как var наперед, но я рекомендую использовать var только если вам обязательно надо изменить значение переменной. Это самый очевидный пункт из моих лучших практик, но это очень важно и стоит упоминания.
Примечание от переводчика: в xCode 7 компилятор автоматически анализирует, меняете ли вы значение у переменной, объявленной как var
и предлагает изменить ее на let
в противном случае.
Предпочитайте приватный(private
) контроль доступа
Опять очевидный пункт, но он также стоит упоминания. Всегда лучше скрывать как можно больше реализации(что означает доступ только из того же исходного файла).
Предпочитайте не опциональные типы
Опциональные типы потрясающие. Этот концепт существует и в других языках(Scala, Haskell, и др).
Но несмотря на то, какими полезными они могут быть, я все же пытаюсь избегать их, если это возможно. Это приводит к более чистому коду, если вы знаете, что переменная или атрибут содержат значение, или что функция возвращает действительное(не nil) значение.
Например, я предпочитаю вызывать исключения в случаях, когда функция не может вернуть действительное(не nil) значение. Посмотрите на NSJSONSerialization.JSONObjectWithData
. Эта функция гарантирует, что она возвращает значение, иначе она вызывает исключение.
Предпочитайте guard let
вместо if let
Что вы можете сделать в if let
блоке? Я почти всегда использую guard let
и ранний возврат из функции, если что-то идет не так. Ранний возврат способствует более легкому чтению кода, т.к вы можете гарантировать корректное состояние переменных. Если вы не можете вычислить обязательное значение, используйте ранний возврат(return).
Не бойтесь вызывать исключения
Вместо раннего возврата, вы можете вызвать исключение, как альтернативную точку выхода из функции. Обработка исключений в Objective-C (@try @catch
) всегда была доступна, хотя использовать эту возможность было не принято. В Swift обработка исключений является фундаментальной идеей и вы должны использовать это.
Например, у меня была функция парсинга, которая возвращала опциональный тип, если данные не могли быть синтаксически разобраны. Это означает, что вызывающая программа должна была проверять возвращаемое значение на nil
. Я отрефакторил эту функцию так, чтобы она всегда возвращала действительное значение и вызывала исключение в противном случае.
Не используйте guard
для многочисленных проверок
Рассмотрим код:
guard let data = data,
son = self.jsonFromData(data),
authors = json["authors"] else {
throw AuthorParserError
}
В нем избегаются повторные вызовы исключения AuthorParserError
, но по моему мнению было бы намного лучше написать так:
guard let data = data else {
throw AuthorParserError
}
guard let son = self.jsonFromData(data) else {
throw AuthorParserError
}
guard let authors = json["authors"] else {
throw AuthorParserError
}
В данном варианте можно протестировать каждый случай по отдельности и вы можете быть уверены, что вы проверили все случаи, иначе Xcode покажет ошибку на неразвернутых опциональных типах.
Всегда вносите зависимости, даже если они предназначены только для тестирования
В swift есть удобная возможность - необязательные аргументы функций. С одной стороны вы можете указывать как использовать функцию по умолчанию, с другой - облегчить тестирование классов, в частности зависимости мок-объектов.
Например, если ваш класс рассчитывает, что кто-либо будет обрабатывать сетевые запросы, то почему бы не передать мок-объект в инициализатор класса?
Это облегчит тестирование, т.к вы можете создать мок-объект для зависимостей и протестировать функциональность одного конкретного класса(Конечно, вы также можете написать более интегрированные тесты).
Например:
public init(requestDelegate: MyClassRequestDelegate = RequestManager()) {
self.requestDelegate = requestDelegate
}
В коде выше зависимость - это на самом деле протокол, который облегчает тестирование, т.к нам достаточно создать мок-объект, который удовлетворяет этому протоколу.
Некоторые программисты могут внести зависимость навсегда, вместо определения значения по умолчанию. Но мне кажется, что главная задача для этого типа зависимостей - облегчить тестирование. Если вы не пишите библиотеку, ваше приложение вероятно всегда использует одну и ту же зависимость, поэтому есть смысл определить ее как зависимость по умолчанию.
Всегда используйте псевдонимы типа(typealias
) для завершающих обработчиков(если это возможно)
Несмотря на то, что синтаксис замыканий может быть запутывающим, синтаксис для псевдонима замыкания очевидно проще, чем эквивалентный typedef
в Objective-C. Поэтому всегда объявляйте псевдоним для завершающих обработчиков(конечно если они не содержат обобщенных типов).
typealias SomethingCompletion = (result: SomeType) -> Void
Используйте перечисления(enum
) для уменьшения неопределенности
В этом фрагменте кода я передаю завершающему обработчику кортеж, который повышает неопределенность.
doSomething() { (output: NSData?, error: NSError?) in
// need to check if we have output or error
}
Заметьте, что я использую
NSError
вместо вызова исключения, т.к невозможно вызывать исключения асинхронно.
Кортеж принимает 2 опциональных значения, что также противоречит тому, о чем о писал выше(используйте не опциональные типы).
Что будет, если мы не передадим ни output
, ни error
? Что если мы передадим значения вовсе?
Вы должны явно передавать аргументы в обработчик и в этом вам могут помочь перечисления. Например, вы могли бы определить перечисление Result
:
enum Result<U> {
case .Success(output: U)
case .Failure(error: NSError)
}
Теперь можно переписать обработчик ошибок так:
doSomething() { result in
switch (result) {
case .Success(let output):
// use output
case .Failure(let error):
// handle error
}
}
Теперь стало абсолютно ясно, что если вызов функции был успешен, мы получим действительный результат(и мы даже будем знать его тип). И наоборот, если вызов неуспешен мы получим ошибку. Результат может быть либо успешным, либо не успешным. Без неопределенности.
Трюк для обобщенного завершающего обработчика
Предположим у вас есть функция, объявленная в протоколе:
protocol ServiceProvider {
func provideService<U where U: AnyService>(completion: (output: U) -> Void)
}
При объявлении класса,
Если при объявлении класса, соответствующего протоколу(т.е реализация методов протокола) вы знаете как создать экземпляр объекта типа U whee U: AnyService
, тогда вы можете вернуть объект соответствующего типа(AnyService
протокол должен предоставлять способ создания/возвращения экземпляра).
В этом случае вызывающий объект определяет какой объект должен быть возвращен в замыкании.
Например:
myOtherClass.provideService { (output: RoomService) in
// do something with the RoomService
}
Реализация provideService
ничего не знает о классе RoomService
. Этот класс просто удовлетворяет протоколу AnyService
благодаря чему можно создать его экземпляры в замыкании.
Мне особенно нравится идея, что вызывающий может получить обратно именно тот объект, который он хочет. Вызывающий объект просто говорит: “В завершающем обработчике я хочу получить экземпляр RoomService
и реализованная функция знает, как это сделать”.
Это полная противоположность блокам в Objective C
, где мы должны были бы объявить аргумент завершающего обработчика с типом id
и вызывающий объект сказал бы компилятору какой тип объекта он ожидает получить назад при реализации завершающего обработчика.
Лучшие практики замыканий
Я пытаюсь сохранять замыкания как можно более краткими и лаконичными.
- Если замыкание возвращает
Void
, не пишите возращаемое значение - Если тип объекта может быть выведен компилятором, не указывайте его тип
- Если замыкание имеет только один аргумент, не помещайте его в скобки
- Всегда передавайте замыкание как последний аргумент
- Всегда используйте trail(хвостовой) синтакс для замыкания, передающихся как последний аргумент
- Не используйте скобки, если компилятор того не требует(например, если функция имеет один аргумент и этот аргумент - замыкание)
Все эти утверждения спорные, но сейчас я имеено так использую замыкания. Например:
myObject.doSomething { output in
// do something with the output
}
вместо:
myObject.doSomething() { (output: NSData?) -> Void in
// do something with the output
}
P.S
Я знаю, что перевод ужасен, именно поэтому я и сделал его(несмотря на то, что в статье используется простой англиский). Но невозможно улучшить какой-либо навык, ничего не делая. The growth mindset и все такое.
Ссылка на оригинал