Существует множество соглашений для языка программирования Swift. Мне больше всего нравится кодовая конвенция для Swift от AirBnB. Если вы видели такие правила, как:
- Используйте PascalCase для имен типов и протоколов и lowerCamelCase для всего остального.
- Называйте булевы именами типа isSpaceShip, hasSpacesuit и т.д. Это дает понять, что это именно булевы, а не другие типы.
То вы знакомы с некоторыми правилами. Многие кодовые конвенции предлагают аналогичные рекомендации.
В течение многих лет я придерживался этой конвенции и обеспечивал ее соблюдение с помощью 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 уделяет первостепенное внимание ясности и описательности в названиях типов. Они не отказываются от длинных названий, если они точно передают назначение или функциональность типа. Такой подход имеет несколько преимуществ:
- Самодокументация: Само имя дает четкое представление о том, что тип делает или представляет.
- Улучшение читаемости кода: Даже не глядя на реализацию, разработчики часто могут понять назначение типа по его имени.
- Уменьшение двусмысленности: Длинные, более конкретные имена помогают различать похожие, но разные понятия.
Применяя этот принцип в собственном коде, учитывайте аналогичный шаблон:
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»).
На этом пока все. Вышеперечисленные пункты — это аспекты, с которыми я сталкивался на протяжении своей карьеры и использовал в нескольких проектах. Они продолжают помогать делать мой код естественным и читабельным. Уверен, что у этой статьи будет вторая часть.
Спасибо за уделенное время. Увидимся в следующий раз!