Site icon AppTractor

Пишите на Swift как Apple

Существует множество соглашений для языка программирования Swift. Мне больше всего нравится кодовая конвенция для Swift от AirBnB. Если вы видели такие правила, как:

То вы знакомы с некоторыми правилами. Многие кодовые конвенции предлагают аналогичные рекомендации.

В течение многих лет я придерживался этой конвенции и обеспечивал ее соблюдение с помощью SwiftLint, полностью полагаясь на один набор правил. Некоторые из этих правил даже отменяли предложения и отступы Xcode.

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

В этой статье я продемонстрирую некоторые из кодовых соглашений, которых я теперь придерживаюсь, непосредственно переняв подход Apple к программированию в Swift.

Булево значение должно определять условие

Пример Apple:

var isPaging: Bool = true
var isUserInteractionEnabled: Bool = false
var isRefreshing: Bool = false

Apple предпочитает, чтобы булевые переменные четко обозначали условие без использования префиксов has, should или is.

class UISwitch: UIControl {
    func setOn(_ on: Bool, animated: Bool)
}

Из контекста setOn видно, что каждый из следующих аргументов в этом методе является условием без префикса.

Объекты реального мира:

struct Door {
    var isLocked: Bool
    var isOpen: Bool

    func open(locked: Bool) {
      if locked {
        isLocked = false
      }

      isOpen = true
    }
}

Не заботьтесь о длине имени типа, уточните намерение

Многие спорят по этому поводу, но Apple делает это четко:

// SwiftUI
class UICollectionViewCompositionalLayoutConfiguration {}
protocol UIViewControllerTransitioningDelegate {}
class UIDocumentBrowserViewController {}
class UIPresentationController {}
class UIAccessibilityCustomAction {}

// UIKit
struct NavigationViewStyleConfiguration {}
protocol PreferenceKey {}
struct EnvironmentValues {}
struct GeometryProxy {}

// Foundation
class JSONDecoder.KeyDecodingStrategy {}
struct DateComponentsFormatter {}
protocol URLSessionTaskDelegate {}
class FileManager.DirectoryEnumerationOptions {}

// CoreData
class NSPersistentStoreCoordinator {}
class NSFetchedResultsControllerDelegate {}
class NSBatchDeleteRequest {}

// Core Animation
class CAMediaTimingFunctionName {}
protocol CALayerDelegate {}

Эти примеры демонстрируют, что Apple уделяет первостепенное внимание ясности и описательности в названиях типов. Они не отказываются от длинных названий, если они точно передают назначение или функциональность типа. Такой подход имеет несколько преимуществ:

  1. Самодокументация: Само имя дает четкое представление о том, что тип делает или представляет.
  2. Улучшение читаемости кода: Даже не глядя на реализацию, разработчики часто могут понять назначение типа по его имени.
  3. Уменьшение двусмысленности: Длинные, более конкретные имена помогают различать похожие, но разные понятия.

Применяя этот принцип в собственном коде, учитывайте аналогичный шаблон:

struct EnvironmentFriendlyVehicleChargingStation {}

protocol UserAuthenticationServiceDelegate {}

class NetworkConnectivityMonitor {}

enum PaymentProcessingStatus {}

Прошедшее время для метода, настоящее время или глагол для замыкания

Apple использует эту конвенцию — посмотрите пример кода:

// Methods

func messaging(
  _ messaging: Messaging, 
  didReceiveRegistrationToken fcmToken: String?
) {}

func viewDidLoad() {}

func application(
  _ application: UIApplication, 
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {}


// Closures
let onValueChanged: ((Int) -> Void)? = { newValue in
    // Handle value change
}

let onReceive: (() -> Void)? = {
    // Handle download completion
}

let onComplete: (() -> Void)? = {
    // Handle tap event
}

dismiss(completion: {})

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

Объект реального мира:

class CoffeeMachine {
    func brewButtonPressed() {
        // Start brewing coffee
    }
    
    var onCupFilled: (() -> Void)?
}

Избегайте прямого указания объекта, используйте Enum или Static для объявления типа

Пример Apple:

// Instead of this:
.buttonStyle(PlainButtonStyle())

// Apple prefers this:
.buttonStyle(.plain)

// Another example:
// Instead of this:
.foregroundColor(Color.red)

// Apple prefers this:
.foregroundColor(.red)

Apple предпочитает использовать статические свойства или кейсы enum для представления объекта или кастомизации. Такой подход делает код более кратким и читабельным. Кроме того, он позволяет улучшить вывод типов и сделать API более гибким.

Объект реального мира:

enum CarType {
    case sedan
    case suv
    case truck
}

struct Car {
    let type: CarType
}

// Instead of this:
let myCar = Car(type: CarType.sedan)

// Apple's style would prefer this:
let myCar = Car(type: .sedan)

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

Вот еще несколько примеров из фреймворков Apple:

// UIKit
view.backgroundColor = .systemBackground

// SwiftUI
Text("Hello")
    .font(.headline)
    .foregroundColor(.primary)

// Core Animation
let animation = CABasicAnimation(keyPath: .position)

// Core Graphics
context.setLineWidth(2)
context.setStrokeColor(.black)

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

Словарь с перечислением в качестве ключа

Использование в Apple:

enum FontWeight {
    case regular, bold, light
}

let fontWeights: [FontWeight: CGFloat] = [
    .regular: 400,
    .bold: 700,
    .light: 300
]
// methods
func application(
  _ application: UIApplication, 
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {}

// usage
let launched = application(app, didFinishLaunchingWithOptions: [.url: "https://uwaisalqadri.com"])

Apple часто использует перечисления в качестве ключей словаря, чтобы создать безопасные для типов, четко определенные пары ключ-значение.

Пример с объектом реального мира:

enum RoomType {
    case bedroom, livingRoom, kitchen, bathroom
}

let roomSizes: [RoomType: Double] = [
    .bedroom: 150.0,
    .livingRoom: 200.0,
    .kitchen: 100.0,
    .bathroom: 50.0
]

Вложенный тип — это нормально

Apple использует такое соглашение:

struct URLRequest {
    enum CachePolicy {
        case useProtocolCachePolicy
        case reloadIgnoringLocalCacheData
        // ...
    }
    // ...
}

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

struct Car {
    enum EngineType {
        case gasoline, electric, hybrid
    }
    
    let engineType: EngineType
    // Other car properties...
}

Протокол — это поведение: найдите нужное существительное, используйте суффикс -able, если оно не найдено

Пример Apple:

protocol Collection { }
protocol Equatable { }
protocol Comparable: Equatable { }

Apple предпочитает называть протоколы существительными, которые описывают поведение или роль, которую протокол представляет. Если подходящего существительного нет, они используют прилагательные, оканчивающиеся на «-able» или «-ible», чтобы описать возможности, предоставляемые протоколом.

Объект реального мира:

protocol Vehicle {
    func move()
}

protocol Recyclable {
    func recycle()
}

struct Car: Vehicle {
    func move() {
        // Implementation...
    }
}

struct PlasticBottle: Recyclable {
    func recycle() {
        // Implementation...
    }
}

В данном примере Vehicle — это существительное, которое четко описывает поведение того, что может двигаться. В слове Recyclable используется суффикс «-able», потому что нет четкого существительного, описывающего поведение того, что может быть переработано.

Такой подход к именованию протоколов помогает сделать код Swift более интуитивным и самодокументирующимся. Он позволяет разработчикам быстро понять назначение протокола по его названию, будь то описание роли (существительное) или возможности (прилагательное с суффиксом «-able»).

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

Спасибо за уделенное время. Увидимся в следующий раз!

Источник

Exit mobile version