Этот стайлгайд создан с целью:
- Облегчить чтение и понимание незнакомого кода
- Облегчить поддержку кода
- Уменьшить вероятность совершения простых ошибок кодинга
- Снизить когнитивную нагрузку при кодинге
- Сфокусировать обсуждения в Pull Request-ах на логике, а не на стиле
Краткость кода не является основной целью. Код должен быть кратким только в том случае, если другие важные качества кода (такие как читаемость, простота и ясность) остаются равными или улучшаются.
- Этот гайд являеся дополнением официальному Swift API Design Guidelines
- Мы стараемся сделать каждое правило проверяемым при помощи различных linter-ов
Вы можете добавить эти настройки воспользовавшись этим скриптом, как вариант, его вызов можно добавить в "Run Script" build phase.
-
# Каждая строка должна иметь максимальную длину в 120 символов.
-
# Используйте 4 пробела для отступов.
-
# Строки не должны содержать пробелы в конце.
-
# Используйте PascalCase для названий типов и протоколов, и lowerCamelCase для всего остального.
protocol SpaceThing { // ... } class SpaceFleet: SpaceThing { enum Formation { // ... } class Spaceship { // ... } var ships: [Spaceship] = [] static let worldName = "Earth" func addShip(_ ship: Spaceship) { // ... } } let myFleet = SpaceFleet()
Исключение: Вы можете поставить префикс подчеркивания перед приватным свойством если оно повторяет свойство или метод с одинаковым именем с более высоким уровнем доступа
Есть некоторые случаи при которых повторение названия свойства или метода может быть проще для чтения и понимания, чем использование другого имени.
Например:
final class BiometricAuthenticator { static var canAuthenticate: Bool { return _canAuthenticate() } ... private static func _canAuthenticate() { ... } }
-
# Называйте булевые переменные в формате
isSpaceship
,hasSpacesuit
, и т.п. Так становится понятнее, что это именно Bool тип данных, а не какой-либо другой. -
# Акронимы в названиях (например
URL
) должны быть в верхнем регистре за исключением случаев, когда это начало названия которое должно быть в lowerCamelCase// Неправильно class UrlValidator { func isValidUrl(_ URL: URL) -> Bool { // ... } func isUrlReachable(_ URL: URL) -> Bool { // ... } } let URLValidator = UrlValidator().isValidUrl(/* some URL */) // Правильно class URLValidator { func isValidURL(_ url: URL) -> Bool { // ... } func isURLReachable(_ url: URL) -> Bool { // ... } } let urlValidator = URLValidator().isValidURL(/* some URL */)
-
# Общая часть названия должна быть впереди, а более специфичная часть должна следовать за ней. Значение "общая часть" зависит от конеткста, но должно примерно означать "то, что больше всего помогает вам сузить поиск нужного элемента." Самое главное, будьте последовательны с тем, как вы располагаете части имен.
// Неправильно let rightTitleMargin: CGFloat let leftTitleMargin: CGFloat let bodyRightMargin: CGFloat let bodyLeftMargin: CGFloat // Правильно let titleMarginRight: CGFloat let titleMarginLeft: CGFloat let bodyMarginRight: CGFloat let bodyMarginLeft: CGFloat
-
# Включите подсказку о типе в имя, если в противном случае оно будет неоднозначным.
// Неправильно let title: String let cancel: UIButton // Правильно let titleText: String let cancelButton: UIButton
-
# Обработчики событий должны быть названы как предложения в настоящем времени. Детали можно опустить, если они не нужны для ясности.
// Неправильно class SomeViewController { private func didTapLogin() { // ... } private func didTapBookButton() { // ... } private func modelDidChange() { // ... } } // Правильно class SomeViewController { private func login() { // ... } private func handleBookButtonTap() { // ... } private func modelChanged() { // ... } }
-
# Названия протоколов должны явно отражать функциональное значение протоколов. Если протокол описывает методы, реализующие действия самого объекта над другими объектами, лучше использовать Noun; если же он описывает действия, которые можно совершить над объектом, реализующим протокол, лучше использовать Adjective с суффиксом -able.
// Неправильно protocol Presenter { func presentInView(view: UIView) } protocol AlertPresentable { func presentAlert(alert: Alert) } // Правильно protocol Presentable { func presentInView(view: UIView) } protocol AlertPresenter { func presentAlert(alert: Alert) }
-
# Не указываете типы там, где они легко могут быть выведены
// Неправильно let host: Host = Host() // Правильно let host = Host()
enum Direction { case left case right } func someDirection() -> Direction { // Неправильно return Direction.left // Правильно return .left }
-
# Условные операторы должны всегда вызывать
return
в следующей строке// Неправильно guard true else { return } if true { return } // Правильно guard true else { return } if true { return }
-
# Не используйте
self
пока это не нужно для уточнения или пока того не требует язык. Это правило не касается инициализаторов, там нужно использоватьself
всегда.final class Listing { init(capacity: Int, allowsPets: Bool) { // Правильно self.capacity = capacity self.isFamilyFriendly = !allowsPets } private let isFamilyFriendly: Bool private var capacity: Int private func increaseCapacity(by amount: Int) { // Неправильно self.capacity += amount self.save() // Правильно capacity += amount save() } }
-
# Следует избегать закрывающей запятой в массивах и словарях
// Неправильно let rowContent = [ listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent(), ] // Правильно let rowContent = [ listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent() ]
-
# Именуйте свойства в кортеже для большей ясности Эмпирическое правило: если у вас есть более 3 полей, вы, вероятно, должны использовать структуру.
// Неправильно func whatever() -> (Int, Int) { return (4, 4) } let thing = whatever() print(thing.0) // Правильно func whatever() -> (x: Int, y: Int) { return (x: 4, y: 4) } // Так тоже можно func whatever2() -> (x: Int, y: Int) { let x = 4 let y = 4 return (x, y) } let coord = whatever() coord.x coord.y
-
# Используйте конструкторы вместо Make() функций для CGRect, CGPoint, NSRange и других.
// Неправильно let rect = CGRectMake(10, 10, 10, 10) // Правильно let rect = CGRect(x: 0, y: 0, width: 10, height: 10)
-
# Используйте современные Swift расширения методов вместо старых глобальных методов из Objective-C.
// Неправильно var rect = CGRectZero var width = CGRectGetWidth(rect) // Правильно var rect = CGRect.zero var width = rect.width
-
# Ставьте двоеточие и пробел сразу после идентификатора.
// Неправильно var something : Double = 0 // Правильно var something: Double = 0
// Неправильно class MyClass : SuperClass { // ... } // Правильно class MyClass: SuperClass { // ... }
// Неправильно var dict = [KeyType:ValueType]() var dict = [KeyType : ValueType]() // Правильно var dict = [KeyType: ValueType]()
-
# Ставьте пробел по обеим сторонам стрелки возвращаемого типа.
// Неправильно func doSomething()->String { // ... } // Правильно func doSomething() -> String { // ... }
// Неправильно func doSomething(completion: ()->Void) { // ... } // Правильно func doSomething(completion: () -> Void) { // ... }
-
# Избегайте лишних скобок.
// Неправильно if (userCount > 0) { ... } switch (someValue) { ... } let evens = userCounts.filter { (number) in number % 2 == 0 } let squares = userCounts.map() { $0 * $0 } // Правильно if userCount > 0 { ... } switch someValue { ... } let evens = userCounts.filter { number in number % 2 == 0 } let squares = userCounts.map { $0 * $0 }
-
# Опустите аргументы case, если они все без имени
// Неправильно if case .done(_) = result { ... } switch animal { case .dog(_, _, _): ... } // Правильно if case .done = result { ... } switch animal { case .dog: ... }
-
# Опускайте возвращаемый тип
Void
.// Неправильно func doSomething() -> Void { ... } // Правильно func doSomething() { ... }
-
# Используйте возвращаемый тип
Void
вместо()
в определении замыкания.// Неправильно func method(completion: () -> ()) { ... } // Правильно func method(completion: () -> Void) { ... }
-
# Именуйте неиспользуемые параметры замыкания как нижние подчеркивания (
_
). -
# Однострочные замыкания должны содержать по одному пробелу до и после каждой скобки, за исключением пробела между закрывающей скобкой и следующим оператором.
// Неправильно let evenSquares = numbers.filter {$0 % 2 == 0}.map { $0 * $0 } // Правильно let evenSquares = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }
-
# Инфиксные операторы должны отделятся одним пробелом с каждой стороны. Предпочитайте скобки, чтобы визуально группировать выражения с большим количеством операторов, а не изменять ширину пробелов. Это правило не относится к операторам диапазона (например,
1...3
) и к префиксным или постфиксным операторам (например,guest?
или-1
).// Неправильно let capacity = 1+2 let capacity = currentCapacity ?? 0 let mask = (UIAccessibilityTraitButton|UIAccessibilityTraitSelected) let capacity=newCapacity let latitude = region.center.latitude - region.span.latitudeDelta/2.0 // Правильно let capacity = 1 + 2 let capacity = currentCapacity ?? 0 let mask = (UIAccessibilityTraitButton | UIAccessibilityTraitSelected) let capacity = newCapacity let latitude = region.center.latitude - (region.span.latitudeDelta / 2.0)
-
# Инициализируйте свойства в
init
где это возможно, а не используйте форс-анвраппинг. Заметным исключением является UIViewController и егоview
свойство.// Неправильно class MyClass: NSObject { var someValue: Int! init() { super.init() someValue = 5 } } // Правильно class MyClass: NSObject { var someValue: Int init() { someValue = 0 super.init() } }
-
# Избегайте выполнение любой значимой или времязатратной работы в
init()
. Избегайте таких действий, как открытие соединения с базой данных, выполнение запросов в сеть, чтение большого объема данных с диска и т.п. Создайте метод вродеstart()
если вам нужно чтобы эти действия были выполнены до того как объект будет готов к использованию. -
# Выносите сложные наблюдатели свойств в методы. Это уменьшает вложенность, отделяет сайд-эффекты от объявления и делает явным использование неявно передаваемых параметров, таких как
oldValue
.// Неправильно class TextField { var text: String? { didSet { guard oldValue != text else { return } // Куча побочных эффектов связанных с текстом } } } // Правильно class TextField { var text: String? { didSet { updateText(from: oldValue) } } private func updateText(from oldValue: String?) { guard oldValue != text else { return } // Куча побочных эффектов связанных с текстом } }
-
# Выносите сложные определения замыканий в методы. Это уменьшает вложенность и сложность использования weak-self в блоках. Если необходимо сослаться на self в вызове замыкания, используйте
guard
, чтобы развернуть self на время вызова.// Неправильно class MyClass { func request(completion: () -> Void) { API.request { [weak self] response in if let strongSelf = self { // Processing and side effects } completion() } } } // Правильно class MyClass { func request(completion: () -> Void) { API.request { [weak self] response in guard let strongSelf = self else { return } strongSelf.doSomething(strongSelf.property) completion() } } func doSomething(nonOptionalParameter: SomeClass) { // Processing and side effects } }
-
# Используйте
guard
в начале скоупа. -
# Контроль доступа должен быть максимально строгим. Предпочитайте использование
public
вместоopen
иprivate
вместоfileprivate
пока вам не понадобится это поведение.// Неправильно final class ViewController { @IBOutlet weak var tableView: UITableView! var models: [Model] = [] func reload() { // ... } } // Правильно final class ViewController { @IBOutlet private weak var tableView: UITableView! private var models: [Model] = [] private func reload() { // ... } }
-
# Избегайте глобальных функций где это возможно. Предпочитайте методы в определениях типов.
// Неправильно func age(of person, bornAt timeInterval) -> Int { // ... } func jump(person: Person) { // ... } // Правильно class Person { var bornAt: TimeInterval var age: Int { // ... } func jump() { // ... } }
-
# Предпочитайте выделять константы в закрытый enum. Если константы должны быть открыты, сделайте их статичными внутри определения класса.
public class MyClass { private enum Constants { static let privateValue = "private" } public static let publicValue = "public" func doSomething() { print(Constants.privateValue) print(MyClass.publicValue) } }
-
# Используйте
enum
без case для организацииpublic
илиinternal
констант и функций в пространства имен. Избегайте создания глобальных констант или функций. Не стесняйтесь вкладывать пространства имен, где это добавляет ясности. -
# Используйте неизменяемые значения где это возможно. Используйте
map
иcompactMap
вместо добавления в новую коллекцию. Используйтеfilter
вмеcто удаления элементов из изменяемой коллекции.Изменяемые свойства увеличивают сложность, поэтому старайтесь держать их в максимально узкой области.
// Неправильно var results = [SomeType]() for element in input { let result = transform(element) results.append(result) } // Правильно let results = input.map { transform($0) }
// Неправильно var results = [SomeType]() for element in input { if let result = transformThatReturnsAnOptional(element) { results.append(result) } } // Правильно let results = input.compactMap { transformThatReturnsAnOptional($0) }
-
# Классы должны быть
final
, если другого не требует логика. -
# Никогда не используйте
default
case вswitch
.Перечисление каждого case требует, чтобы разработчики и ревьюеры учитывали правильность каждого оператора switch при добавлении новых case.
// Неправильно switch anEnum { case .a: // Do something default: // Do something else. } // Правильно switch anEnum { case .a: // Do something case .b, .c: // Do something else. }
-
# Проверьте значение nil вместо использования разворачивания, если значение не требуется.
-
# Сортируйте импорты по алфавиту и ставьте их после комментариев в заголовке файла. Если одна из библиотек уже импортирует какие-то библиотеки необходимые в вашем модуле - их импорты можно опустить.
Стандартный метод организации помогает инженерам быстрее определить, от каких модулей зависит файл.
// Неправильно // Copyright © 2018 Surf. All rights reserved. // import DLSPrimitives import Constellation import Epoxy import UIKit import Foundation // Правильно // Copyright © 2018 Surf. All rights reserved. // import Constellation import DLSPrimitives import Epoxy import UIKit
Исключение:
@testable import
должны быть сгурппированы после обычных import и разделены пустой строкой.// Неправильно // Copyright © 2018 Surf. All rights reserved. // import DLSPrimitives @testable import Epoxy import Foundation import Nimble import Quick // Правильно // Copyright © 2018 Surf. All rights reserved. // import DLSPrimitives import Foundation import Nimble import Quick @testable import Epoxy
-
# Ограничьте пустые вертикальные пробелы одной строкой.
-
# Файлы должны заканчиваться новой строкой.
-
# Старайтесь избегать наследования от NSObject. Если ваш код должен быть использован каким-нибудь Objective-C кодом оберните его чтобы предоставить необходимую функциональность. Используйте
@objc
для отдельных функций и переменных вместо предоставления всего API класса при помощи@objcMembers
.class PriceBreakdownViewController { private let acceptButton = UIButton() private func setUpAcceptButton() { acceptButton.addTarget( self, action: #selector(didTapAcceptButton), forControlEvents: .TouchUpInside) } @objc private func didTapAcceptButton() { // ... } }