This codestyle is based on two documents:
and adds some overrides and additions to the latter.
Before reading this document, be sure you are familiar with (1) and (2).
Shoutout to Airbnb team and their styleguide for certain well-stated points.
-
Use 4 spaces to indent lines
-
guard
andif
For one expression:
guard fruitCount > 5 else { return } // (linebreak allowed) if fruitCount < 7 { print("need more fruit for this pie") }
When multline:
guard case let .upsert(message) = anyMessage, case let .text(textMessage) = message, let correlationID = textMessage.content.correlationID else { return .delete($0.id) } // linebreak allowed
-
Omit
return
in functions with single expression
-
throws
vs-> ReturnT?
Prefer throwing an error instead of returning optional in function of initializer:
// bad: why did it fail? func parse(encoded value: Any) -> String? { // ... } // good func parse(encoded value: Any) throws -> String { guard let dictionary = value as? [String: String] else { throw ParseError("not a dictionary") } guard let value = dictionary["id"] else { throw ParseError("`id` not presented") } // ... }
-
Namespaces
Subclass
Namespace
class to make it clear that the type is just a namespace:// bad enum Config { static let lengthRange = 10...50 } // good class Config: Namespace { static let lengthRange = 10...50 }
-
Vertical white space
Add single blank line to separate code parts:
// ... // Copyright © 2020 SoftPro. All rights reserved. // import Foundation final class EventViewModel { // ... } // MARK: - Auxillary Types extension EventViewModel { struct Cache { // ... } } // MARK: - EventModelDelegate extension EventViewModel: EventModelDelegate { func model(_ eventModel: EventModel, didUpdateEventFor id: Event.ID) { // ... } } EOF
-
General rules
- Word in plural form can only be last part of the name (with rare exceptions):
let eventsModel: EventsModel // bad let eventModel: EventModel // good let objectsCount: Int // bad let objectCount: Int // good let teamsProvider: Provider<Team> // bad let teamProvider: Provider<Team> // good func subscribeOnChatNotifications() { ... } // good var eventsByID: [Event.ID: Event] // exception, good
- Word in plural form can only be last part of the name (with rare exceptions):
-
Variables
-
For
String
, Numeric types,Bool
andDate
omit the type name:var title: String var id: String var personCount: Int var bottomMargin: CGFloat var isHidden: Bool var shouldInvalidate: Bool var createdAt: Date
-
For
Array
use plural form:var eventModels: [EventModel] var models: [EventModel] // also good if in `Event` context
-
For
Dictionary
use pattern<ObjectT>sBy<KeyT>
(with some exceptions):var eventsById: [Event.ID: Event] var addressesByPersonName: [String: String] var jsonDictionary: [String: Any] // also OK, in case of poor semantics
-
For other types, name should end either with the name of the type or it's suffix:
var eventSet: Set<Event> var containerView: UIView var stackView: UIStackView var titleLabel: UILabel var eventStatus: EventStatus var status: EventStatus // also OK if in `Event` context var eventChange: EntityCollectionChange<Event>
-
-
Abbreviations
Abbreviations are prohibited:
// bad var subs: Subscription var s: String var params: [String: String] var val: NSValue var i: Int
Exceptions are:
str (string) config (configuration) dict (dictionary)
Abbreviations from the list above do not reduce clarity, improve code readability and are highly recommeded to use
-
Acronyms
Acronyms in names (e.g. URL) should be all-caps except when it’s the start of a name that would otherwise be lowerCamelCase, in which case it should be uniformly lower-cased:
class URLValidator { func isValidURL(_ url: URL) -> Bool { ... } func isProfileURL(_ url: URL, for userID: String) -> Bool { ... } } let urlValidator = URLValidator() let isProfile = urlValidator.isProfileUrl(urlToTest, userID: idOfUser)
Exception: although
id
is not an acronym, the rules for it are the same -
Generics
For generic type and function declarations, name placeholder types using
<Placeholder>T
pattern:public func index<CollectionT: Collection, ElementT>( of element: ElementT, in collection: CollectionT ) throws -> CollectionT.Index where CollectionT.Element == ElementT, ElementT: Equatable { for element in collection { // ... } }
For generic protocols, name the associated type without
T
suffix:protocol EntityType { associatedtype ID var id: ID { get } }
-
guard
usagePrefer using
guard
at the beginning of a scope. It's easier to reason about a block of code when all guard statements are grouped together at the top rather than intermixed with business logic. Useif
otherwise. -
property declarations
-
Use
let
for properties with values injected through the initializer -
Use
var
for computed, mutable properties or optional external dependencies (e.g. delegate, dataSource) -
Use
lazy var
to avoid implicitly unwrapped optionals
Use implace property initialization for lazy and mutable properties.
If property intialization doesn't fit into one line, extract it into
make<PropertyName>
function. Property observers should also fit into one line or being extracted.class EventViewModel { var statusObservable: Observable<Status> { statusRelay.asObservable } weak var delegate: EventViewModelDelegate? { didSet { delegate.viewModel(self, updatedStatus: statusRelay.value) } } private(set) var cachedHeaderHeight: CGFloat = 0.0 { didSet { print("cache updated") } } private let model: EventModel private let soundEffectPlayer: SoundEffectPlayer private let config = Config() private let bag = DisposeBag() private lazy var dateFormatter = makeDateFormatter() private lazy var statusRelay = BehaviorRelay(value: .idle) init( model: EventModel, soundEffectPlayer: SoundEffectPlayer ) { self.model = model self.soundEffectPlayer = soundEffectPlayer } // ... }
-
-
Declaration order (TODO)
class MyClass { var publicVar: Int = 0 let publicLet: Int private var privateVar: Int private lazy var privateLazyVar: Int private let privateLet: Int init() { ... } override func overridedMethod() { ... } func publicMethod() { ... } private func privateMethod() { ... } } extension MyClass { static func publicStaticMethod() { ... } private static func privateStaticMethod() { ... } } extension MyClass { enum MyAuxillaryEnum { case a case b } enum MyAuxillaryStruct { var text: String var counter: Int } }
-
Splitting up into extensions (TODO)
Avoid using extensions on your class for splitting logic purposes. Use marks instead.
Use extensions:
- to implement protocol conformance
- when it can be extracted to another file
TODO: Add an example
-
Use
Pure
namespace to group pure functionsextension MyClass { class Pure: Namespace { static func pureFunction1(a: Int) -> Int { ... } static func pureFunction2(b: Int, c: String) -> String { ... } } }
-
fallback(...)
andfatalError(...)
Handle an unexpected but recoverable condition with an
fallback
function:func eatFruit(at index: Int) { guard index < fruits.count else { return fallback() } // ... } func nameForFruit(_ fruit: Fruit) -> String { guard knownFruits.contains(fruit) else { return fallback("Unnamed") } // ... }
If the unexpected condition is not recoverable, prefer
fatalError
:func fruit(at: index) -> Fruit { guard index < fruits.count else { fatalError(.shouldNeverBeCalled) } // ... }
-
strongify(self) { ... }
When capturing
self
in a closure, usestrongify
instead of cumbersomeweakify-strongify self
routine.For now, avoid using
strongify
with closures, sincesself
is ugly:// avoid authModel.unauthorize .observeOn(MainScheduler.instance) .subscribe( onNext: strongify(self) { sself, error in sself.didReceiveLoginError(error) sself.settingsModel.eraseData() sself.initUserInterface() } ).disposed(by: disposeBag)
strongify
often allows you to avoid curly brackets:// Somewhere in CollectionReloadCoordinator class... // bad elementsProvider.observable .subscribe( onNext: { [weak self] elements in self?.onUpdateElements(elements) } ) .disposed(by: bag) // good elementsProvider.observable .subscribe(onNext: strongify(self, CollectionReloadCoordinator.onUpdateElements)) .disposed(by: bag)
In case
self
isnil
at the moment of invocation, strongified function returnsnil
. Use function composition operator (>>>
) to remove optionality from the return value of strongified call:// in EventViewModel: eventModel.statusObservable .filter(strongify(self, EventViewModel.shouldUpdateForStatus) >>> { $0 ?? false }) .subscribe(onNext: strongify(self, EventViewModel.update)) .disposed(by: bag)
-
Indentation
Example of correctly indented code:
actionObservable .filter { $0 == .startLoadNextPage } .withLatestFrom(stateObservable.map { $0.feed }) // one line is OK for single rx-statement .flatMap { state in // use named parameters for multline rx-statement messageService.loadMessages(before: state.lastMessageDate(), limit: state.pageSize) .observeOn(MainScheduler.instance) .asObservable() .map(FeedAction.finishLoadNextPage) .catchError { _ in .empty() } } .subscribe( onNext: { action in // ... }, onError: { error in // ... } ).disposed(by: bag) // the only statement allowed not to start with a line break
-
Dispose bags
Omit
dispose
word when namingDisposeBag
's:// Single default bag lazy var bag = DisposeBag() // Specific bag lazy var subscriptionBag = DisposeBag()
-
Abbreviations (TODO?)
Allowed abbreviations:
actionObsevable
->$action
TODO
Comments: 1. if, guard — ранний выход
if, guard — без однострочности
Исключение — ID — с большой
-
Single-expression func: ньюанс Двустрочность
-
Неймспейсы (пример другой)
-
Марки — на свое усмотрение
-
Пустые строки — не ставим после объ типа и экстеншна
VC, VM — не пишем i — не пишем
-
lazy — mutable
-
didSet, instantiate — oneline
11 Порядок var/let — todo примеры
12 Не бьем класс на экстеншны, только по признаку протоколов + то, что можно унести в отдельный файл
13 Declaration order
static - в конце types - в конце
14 Pure
15 strongify - только 1 строка (метод)
16 ctrl + i recommednation
17 switch — 1 или 2 строки
18 utils, helpers?
19 file naming: +, ., ... (Google)
- Можно ссылаться на эппл гайд и гугл гайд
TODO: discuss distinctions from Google's guide:
- Private extensions vs private functions, #access-levels
- Redundant imports (Foundation + UIKit), #import-statements