Surf Swift Style Guide

Цели

Этот стайлгайд создан с целью:

  • Облегчить чтение и понимание незнакомого кода
  • Облегчить поддержку кода
  • Уменьшить вероятность совершения простых ошибок кодинга
  • Снизить когнитивную нагрузку при кодинге
  • Сфокусировать обсуждения в Pull Request-ах на логике, а не на стиле

Краткость кода не является основной целью. Код должен быть кратким только в том случае, если другие важные качества кода (такие как читаемость, простота и ясность) остаются равными или улучшаются.

Принципы

  • Этот гайд являеся дополнением официальному Swift API Design Guidelines
  • Мы стараемся сделать каждое правило проверяемым при помощи различных linter-ов

Содержание

Xcode форматирование

Вы можете добавить эти настройки воспользовавшись этим скриптом, как вариант, его вызов можно добавить в "Run Script" build phase.

  • # Каждая строка должна иметь максимальную длину в 120 символов. SwiftLint: line_length

  • # Используйте 4 пробела для отступов.

  • # Строки не должны содержать пробелы в конце. SwiftFormat: trailingSpace SwiftLint: trailing_whitespace

Именование

  • # Используйте PascalCase для названий типов и протоколов, и lowerCamelCase для всего остального. SwiftLint: type_name

    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 в следующей строке SwiftLint: conditional_returns_on_newline

    // Неправильно
    guard true else { return }
    
    if true { return }
    
    // Правильно
    guard true else {
      return
    }
    
    if true {
      return
    }
  • # Не используйте self пока это не нужно для уточнения или пока того не требует язык. Это правило не касается инициализаторов, там нужно использовать self всегда. SwiftFormat: redundantSelf

    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()
      }
    }
  • # Следует избегать закрывающей запятой в массивах и словарях SwiftLint: trailing_comma

    // Неправильно
    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 и других. SwiftLint: legacy_constructor

    // Неправильно
    let rect = CGRectMake(10, 10, 10, 10)
    
    // Правильно
    let rect = CGRect(x: 0, y: 0, width: 10, height: 10)
  • # Используйте современные Swift расширения методов вместо старых глобальных методов из Objective-C. SwiftLint: legacy_cggeometry_functions SwiftLint: legacy_constant SwiftLint: legacy_nsgeometry_functions

    // Неправильно
    var rect = CGRectZero
    var width = CGRectGetWidth(rect)
    
    // Правильно
    var rect = CGRect.zero
    var width = rect.width
  • # Ставьте двоеточие и пробел сразу после идентификатора. SwiftLint: colon

    // Неправильно
    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]()
  • # Ставьте пробел по обеим сторонам стрелки возвращаемого типа. SwiftLint: return_arrow_whitespace

    // Неправильно
    func doSomething()->String {
      // ...
    }
    
    // Правильно
    func doSomething() -> String {
      // ...
    }
    // Неправильно
    func doSomething(completion: ()->Void) {
      // ...
    }
    
    // Правильно
    func doSomething(completion: () -> Void) {
      // ...
    }
  • # Избегайте лишних скобок. SwiftFormat: redundantParens

    // Неправильно
    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, если они все без имени SwiftLint: empty_enum_arguments

    // Неправильно
    if case .done(_) = result { ... }
    
    switch animal {
    case .dog(_, _, _):
      ...
    }
    
    // Правильно
    if case .done = result { ... }
    
    switch animal {
    case .dog:
      ...
    }

Функции

  • # Опускайте возвращаемый тип Void. SwiftLint: redundant_void_return

    // Неправильно
    func doSomething() -> Void {
      ...
    }
    
    // Правильно
    func doSomething() {
      ...
    }

Замыкания

  • # Используйте возвращаемый тип Void вместо () в определении замыкания. SwiftLint: void_return

    // Неправильно
    func method(completion: () -> ()) {
      ...
    }
    
    // Правильно
    func method(completion: () -> Void) {
      ...
    }
  • # Именуйте неиспользуемые параметры замыкания как нижние подчеркивания (_). SwiftLint: unused_closure_parameter

    Почему?

    Это упрощает чтение, так становится очевидно какие параметры используются, а какие не используются.

    // Неправильно
    someAsyncThing() { argument1, argument2, argument3 in
      print(argument3)
    }
    
    // Правильно
    someAsyncThing() { _, _, argument3 in
      print(argument3)
    }
  • # Однострочные замыкания должны содержать по одному пробелу до и после каждой скобки, за исключением пробела между закрывающей скобкой и следующим оператором. SwiftLint: closure_spacing

    // Неправильно
    let evenSquares = numbers.filter {$0 % 2 == 0}.map {  $0 * $0  }
    
    // Правильно
    let evenSquares = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }

Операторы

  • # Инфиксные операторы должны отделятся одним пробелом с каждой стороны. Предпочитайте скобки, чтобы визуально группировать выражения с большим количеством операторов, а не изменять ширину пробелов. Это правило не относится к операторам диапазона (например, 1...3) и к префиксным или постфиксным операторам (например, guest? или -1). SwiftLint: operator_usage_whitespace

    // Неправильно
    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 свойство. SwiftLint: implicitly_unwrapped_optional

    // Неправильно
    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 в начале скоупа.

    Почему?

    Проще рассуждать о блоке кода, когда все операторы 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 констант и функций в пространства имен. Избегайте создания глобальных констант или функций. Не стесняйтесь вкладывать пространства имен, где это добавляет ясности.

    Почему?

    enum-ы без case хорошо работают как пространства имен так как они не могут быть созданы, что соответствует их назначению.

    enum Environment {
    
      enum Earth {
        static let gravity = 9.8
      }
    
      enum Moon {
        static let gravity = 1.6
      }
    }
  • # Используйте неизменяемые значения где это возможно. Используйте 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, если другого не требует логика.

    Почему?

    Если класс должен быть переопределен, автор должен указать эту функциональность, опуская ключевое слово final.

    // Неправильно
    class SettingsRepository {
      // ...
    }
    
    // Правильно
    final class SettingsRepository {
      // ...
    }
  • # Никогда не используйте 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 вместо использования разворачивания, если значение не требуется. SwiftLint: unused_optional_binding

Организация файлов

  • # Сортируйте импорты по алфавиту и ставьте их после комментариев в заголовке файла. Если одна из библиотек уже импортирует какие-то библиотеки необходимые в вашем модуле - их импорты можно опустить. SwiftFormat: sortedImports

    Почему?

    Стандартный метод организации помогает инженерам быстрее определить, от каких модулей зависит файл.

    // Неправильно
    
    //  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
  • # Ограничьте пустые вертикальные пробелы одной строкой. SwiftLint: vertical_whitespace

  • # Файлы должны заканчиваться новой строкой. SwiftLint: trailing_newline

Совместимость с Objective-C

  • # Старайтесь избегать наследования от 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() {
        // ...
      }
    }

Swiftcodestyle

Style guide for writing in Swift.

Swiftcodestyle Info

⭐ Stars 17
🔗 Source Code github.com
🕒 Last Update 7 months ago
🕒 Created 6 years ago
🐞 Open Issues 3
➗ Star-Issue Ratio 6
😎 Author surfstudio