├── LICENSE
├── README.md
└── resources
├── surf.swiftformat
├── swiftlint.yml
└── xcode_settings.bash
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Surf
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Surf Swift Style Guide
2 |
3 | ## Цели
4 |
5 | Этот стайлгайд создан с целью:
6 |
7 | * Облегчить чтение и понимание незнакомого кода
8 | * Облегчить поддержку кода
9 | * Уменьшить вероятность совершения простых ошибок кодинга
10 | * Снизить когнитивную нагрузку при кодинге
11 | * Сфокусировать обсуждения в Pull Request-ах на логике, а не на стиле
12 |
13 | Краткость кода не является основной целью. Код должен быть кратким только в том случае, если другие важные качества кода (такие как читаемость, простота и ясность) остаются равными или улучшаются.
14 |
15 | ## Принципы
16 |
17 | * Этот гайд являеся дополнением официальному [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/)
18 | * Мы стараемся сделать каждое правило проверяемым при помощи различных linter-ов
19 |
20 | ## Содержание
21 |
22 | - [Surf Swift Style Guide](#surf-swift-style-guide)
23 | - [Цели](#%D1%86%D0%B5%D0%BB%D0%B8)
24 | - [Принципы](#%D0%BF%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF%D1%8B)
25 | - [Содержание](#%D1%81%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5)
26 | - [Xcode форматирование](#xcode-%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
27 | - [Именование](#%D0%B8%D0%BC%D0%B5%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
28 | - [Стиль](#%D1%81%D1%82%D0%B8%D0%BB%D1%8C)
29 | - [Функции](#%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8)
30 | - [Замыкания](#%D0%B7%D0%B0%D0%BC%D1%8B%D0%BA%D0%B0%D0%BD%D0%B8%D1%8F)
31 | - [Операторы](#%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80%D1%8B)
32 | - [Паттерны](#%D0%BF%D0%B0%D1%82%D1%82%D0%B5%D1%80%D0%BD%D1%8B)
33 | - [Организация файлов](#%D0%BE%D1%80%D0%B3%D0%B0%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-%D1%84%D0%B0%D0%B9%D0%BB%D0%BE%D0%B2)
34 | - [Совместимость с Objective-C](#%D1%81%D0%BE%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C-%D1%81-objective-c)
35 |
36 | ## Xcode форматирование
37 |
38 | _Вы можете добавить эти настройки воспользовавшись [этим скриптом](resources/xcode_settings.bash), как вариант, его вызов можно добавить в "Run Script" build phase._
39 |
40 | * # **Каждая строка должна иметь максимальную длину в 120 символов.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#line-length)
41 |
42 | * # **Используйте 4 пробела для отступов.**
43 |
44 | * # **Строки не должны содержать пробелы в конце.** [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#trailingSpace) [](https://github.com/realm/SwiftLint/blob/master/Rules.md#trailing-whitespace)
45 |
46 | ## Именование
47 |
48 | * # **Используйте PascalCase для названий типов и протоколов, и lowerCamelCase для всего остального.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#type-name)
49 |
50 |
51 |
52 | ```swift
53 | protocol SpaceThing {
54 | // ...
55 | }
56 |
57 | class SpaceFleet: SpaceThing {
58 |
59 | enum Formation {
60 | // ...
61 | }
62 |
63 | class Spaceship {
64 | // ...
65 | }
66 |
67 | var ships: [Spaceship] = []
68 | static let worldName = "Earth"
69 |
70 | func addShip(_ ship: Spaceship) {
71 | // ...
72 | }
73 | }
74 |
75 | let myFleet = SpaceFleet()
76 | ```
77 |
78 |
79 |
80 | _Исключение: Вы можете поставить префикс подчеркивания перед приватным свойством если оно повторяет свойство или метод с одинаковым именем с более высоким уровнем доступа_
81 |
82 |
83 |
84 | #### Почему?
85 | Есть некоторые случаи при которых повторение названия свойства или метода может быть проще для чтения и понимания, чем использование другого имени.
86 |
87 | Например:
88 |
89 | ```swift
90 | final class BiometricAuthenticator {
91 |
92 | static var canAuthenticate: Bool {
93 | return _canAuthenticate()
94 | }
95 |
96 | ...
97 |
98 | private static func _canAuthenticate() {
99 | ...
100 | }
101 |
102 | }
103 | ```
104 |
105 |
106 |
107 | * # **Называйте булевые переменные в формате `isSpaceship`, `hasSpacesuit`, и т.п.** Так становится понятнее, что это именно Bool тип данных, а не какой-либо другой.
108 |
109 | * # **Акронимы в названиях (например `URL`) должны быть в верхнем регистре за исключением случаев, когда это начало названия которое должно быть в lowerCamelCase**
110 |
111 |
112 |
113 | ```swift
114 | // Неправильно
115 | class UrlValidator {
116 |
117 | func isValidUrl(_ URL: URL) -> Bool {
118 | // ...
119 | }
120 |
121 | func isUrlReachable(_ URL: URL) -> Bool {
122 | // ...
123 | }
124 | }
125 |
126 | let URLValidator = UrlValidator().isValidUrl(/* some URL */)
127 |
128 | // Правильно
129 | class URLValidator {
130 |
131 | func isValidURL(_ url: URL) -> Bool {
132 | // ...
133 | }
134 |
135 | func isURLReachable(_ url: URL) -> Bool {
136 | // ...
137 | }
138 | }
139 |
140 | let urlValidator = URLValidator().isValidURL(/* some URL */)
141 | ```
142 |
143 |
144 |
145 | * # **Общая часть названия должна быть впереди, а более специфичная часть должна следовать за ней.** Значение "общая часть" зависит от конеткста, но должно примерно означать "то, что больше всего помогает вам сузить поиск нужного элемента." Самое главное, будьте последовательны с тем, как вы располагаете части имен.
146 |
147 |
148 |
149 | ```swift
150 | // Неправильно
151 | let rightTitleMargin: CGFloat
152 | let leftTitleMargin: CGFloat
153 | let bodyRightMargin: CGFloat
154 | let bodyLeftMargin: CGFloat
155 |
156 | // Правильно
157 | let titleMarginRight: CGFloat
158 | let titleMarginLeft: CGFloat
159 | let bodyMarginRight: CGFloat
160 | let bodyMarginLeft: CGFloat
161 | ```
162 |
163 |
164 |
165 | * # **Включите подсказку о типе в имя, если в противном случае оно будет неоднозначным.**
166 |
167 |
168 |
169 | ```swift
170 | // Неправильно
171 | let title: String
172 | let cancel: UIButton
173 |
174 | // Правильно
175 | let titleText: String
176 | let cancelButton: UIButton
177 | ```
178 |
179 |
180 |
181 | * # **Обработчики событий должны быть названы как предложения в настоящем времени.** Детали можно опустить, если они не нужны для ясности.
182 |
183 |
184 |
185 | ```swift
186 | // Неправильно
187 | class SomeViewController {
188 |
189 | private func didTapLogin() {
190 | // ...
191 | }
192 |
193 | private func didTapBookButton() {
194 | // ...
195 | }
196 |
197 | private func modelDidChange() {
198 | // ...
199 | }
200 | }
201 |
202 | // Правильно
203 | class SomeViewController {
204 |
205 | private func login() {
206 | // ...
207 | }
208 |
209 | private func handleBookButtonTap() {
210 | // ...
211 | }
212 |
213 | private func modelChanged() {
214 | // ...
215 | }
216 | }
217 | ```
218 |
219 |
220 | * # **Названия протоколов должны явно отражать функциональное значение протоколов**. Если протокол описывает методы, реализующие действия самого объекта над другими объектами, лучше использовать Noun; если же он описывает действия, которые можно совершить над объектом, реализующим протокол, лучше использовать Adjective с суффиксом -able.
221 |
222 |
223 |
224 |
225 | ```swift
226 | // Неправильно
227 | protocol Presenter {
228 | func presentInView(view: UIView)
229 | }
230 |
231 | protocol AlertPresentable {
232 | func presentAlert(alert: Alert)
233 | }
234 |
235 | // Правильно
236 | protocol Presentable {
237 | func presentInView(view: UIView)
238 | }
239 |
240 | protocol AlertPresenter {
241 | func presentAlert(alert: Alert)
242 | }
243 | ```
244 |
245 |
246 |
247 | ## Стиль
248 |
249 | * # **Не указываете типы там, где они легко могут быть выведены**
250 |
251 |
252 |
253 | ```swift
254 | // Неправильно
255 | let host: Host = Host()
256 |
257 | // Правильно
258 | let host = Host()
259 | ```
260 |
261 | ```swift
262 | enum Direction {
263 | case left
264 | case right
265 | }
266 |
267 | func someDirection() -> Direction {
268 | // Неправильно
269 | return Direction.left
270 |
271 | // Правильно
272 | return .left
273 | }
274 | ```
275 |
276 |
277 |
278 | * # **Условные операторы должны всегда вызывать `return` в следующей строке** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#conditional-returns-on-newline)
279 |
280 |
281 |
282 | ```swift
283 | // Неправильно
284 | guard true else { return }
285 |
286 | if true { return }
287 |
288 | // Правильно
289 | guard true else {
290 | return
291 | }
292 |
293 | if true {
294 | return
295 | }
296 | ```
297 |
298 |
299 |
300 | * # **Не используйте `self` пока это не нужно для уточнения или пока того не требует язык.** Это правило не касается инициализаторов, там нужно использовать `self` всегда. [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#redundantSelf)
301 |
302 |
303 |
304 | ```swift
305 | final class Listing {
306 |
307 | init(capacity: Int, allowsPets: Bool) {
308 | // Правильно
309 | self.capacity = capacity
310 | self.isFamilyFriendly = !allowsPets
311 | }
312 |
313 | private let isFamilyFriendly: Bool
314 | private var capacity: Int
315 |
316 | private func increaseCapacity(by amount: Int) {
317 | // Неправильно
318 | self.capacity += amount
319 | self.save()
320 |
321 | // Правильно
322 | capacity += amount
323 | save()
324 | }
325 | }
326 | ```
327 |
328 |
329 |
330 | * # **Следует избегать закрывающей запятой в массивах и словарях** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#trailing-comma)
331 |
332 |
333 |
334 | ```swift
335 | // Неправильно
336 | let rowContent = [
337 | listingUrgencyDatesRowContent(),
338 | listingUrgencyBookedRowContent(),
339 | listingUrgencyBookedShortRowContent(),
340 | ]
341 |
342 | // Правильно
343 | let rowContent = [
344 | listingUrgencyDatesRowContent(),
345 | listingUrgencyBookedRowContent(),
346 | listingUrgencyBookedShortRowContent()
347 | ]
348 | ```
349 |
350 |
351 |
352 | * # **Именуйте свойства в кортеже для большей ясности** Эмпирическое правило: если у вас есть более 3 полей, вы, вероятно, должны использовать структуру.
353 |
354 |
355 |
356 | ```swift
357 | // Неправильно
358 | func whatever() -> (Int, Int) {
359 | return (4, 4)
360 | }
361 | let thing = whatever()
362 | print(thing.0)
363 |
364 | // Правильно
365 | func whatever() -> (x: Int, y: Int) {
366 | return (x: 4, y: 4)
367 | }
368 |
369 | // Так тоже можно
370 | func whatever2() -> (x: Int, y: Int) {
371 | let x = 4
372 | let y = 4
373 | return (x, y)
374 | }
375 |
376 | let coord = whatever()
377 | coord.x
378 | coord.y
379 | ```
380 |
381 |
382 |
383 | * # **Используйте конструкторы вместо Make() функций для CGRect, CGPoint, NSRange и других.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#legacy-constructor)
384 |
385 |
386 |
387 | ```swift
388 | // Неправильно
389 | let rect = CGRectMake(10, 10, 10, 10)
390 |
391 | // Правильно
392 | let rect = CGRect(x: 0, y: 0, width: 10, height: 10)
393 | ```
394 |
395 |
396 |
397 | * # **Используйте современные Swift расширения методов вместо старых глобальных методов из Objective-C.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#legacy-cggeometry-functions) [](https://github.com/realm/SwiftLint/blob/master/Rules.md#legacy-constant) [](https://github.com/realm/SwiftLint/blob/master/Rules.md#legacy-nsgeometry-functions)
398 |
399 |
400 |
401 | ```swift
402 | // Неправильно
403 | var rect = CGRectZero
404 | var width = CGRectGetWidth(rect)
405 |
406 | // Правильно
407 | var rect = CGRect.zero
408 | var width = rect.width
409 | ```
410 |
411 |
412 |
413 | * # **Ставьте двоеточие и пробел сразу после идентификатора.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#colon)
414 |
415 |
416 |
417 | ```swift
418 | // Неправильно
419 | var something : Double = 0
420 |
421 | // Правильно
422 | var something: Double = 0
423 | ```
424 |
425 | ```swift
426 | // Неправильно
427 | class MyClass : SuperClass {
428 | // ...
429 | }
430 |
431 | // Правильно
432 | class MyClass: SuperClass {
433 | // ...
434 | }
435 | ```
436 |
437 | ```swift
438 | // Неправильно
439 | var dict = [KeyType:ValueType]()
440 | var dict = [KeyType : ValueType]()
441 |
442 | // Правильно
443 | var dict = [KeyType: ValueType]()
444 | ```
445 |
446 |
447 |
448 | * # **Ставьте пробел по обеим сторонам стрелки возвращаемого типа.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#returning-whitespace)
449 |
450 |
451 |
452 | ```swift
453 | // Неправильно
454 | func doSomething()->String {
455 | // ...
456 | }
457 |
458 | // Правильно
459 | func doSomething() -> String {
460 | // ...
461 | }
462 | ```
463 |
464 | ```swift
465 | // Неправильно
466 | func doSomething(completion: ()->Void) {
467 | // ...
468 | }
469 |
470 | // Правильно
471 | func doSomething(completion: () -> Void) {
472 | // ...
473 | }
474 | ```
475 |
476 |
477 |
478 | * # **Избегайте лишних скобок.** [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#redundantParens)
479 |
480 |
481 |
482 | ```swift
483 | // Неправильно
484 | if (userCount > 0) { ... }
485 | switch (someValue) { ... }
486 | let evens = userCounts.filter { (number) in number % 2 == 0 }
487 | let squares = userCounts.map() { $0 * $0 }
488 |
489 | // Правильно
490 | if userCount > 0 { ... }
491 | switch someValue { ... }
492 | let evens = userCounts.filter { number in number % 2 == 0 }
493 | let squares = userCounts.map { $0 * $0 }
494 | ```
495 |
496 |
497 |
498 | * # **Опустите аргументы case, если они все без имени** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#empty-enum-arguments)
499 |
500 |
501 |
502 | ```swift
503 | // Неправильно
504 | if case .done(_) = result { ... }
505 |
506 | switch animal {
507 | case .dog(_, _, _):
508 | ...
509 | }
510 |
511 | // Правильно
512 | if case .done = result { ... }
513 |
514 | switch animal {
515 | case .dog:
516 | ...
517 | }
518 | ```
519 |
520 |
521 |
522 | ### Функции
523 |
524 | * # **Опускайте возвращаемый тип `Void`.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#redundant-void-return)
525 |
526 |
527 |
528 | ```swift
529 | // Неправильно
530 | func doSomething() -> Void {
531 | ...
532 | }
533 |
534 | // Правильно
535 | func doSomething() {
536 | ...
537 | }
538 | ```
539 |
540 |
541 |
542 | ### Замыкания
543 |
544 | * # **Используйте возвращаемый тип `Void` вместо `()` в определении замыкания.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#void-return)
545 |
546 |
547 |
548 | ```swift
549 | // Неправильно
550 | func method(completion: () -> ()) {
551 | ...
552 | }
553 |
554 | // Правильно
555 | func method(completion: () -> Void) {
556 | ...
557 | }
558 | ```
559 |
560 |
561 |
562 | * # **Именуйте неиспользуемые параметры замыкания как нижние подчеркивания (`_`).** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#unused-closure-parameter)
563 |
564 |
565 |
566 | #### Почему?
567 | Это упрощает чтение, так становится очевидно какие параметры используются, а какие не используются.
568 |
569 | ```swift
570 | // Неправильно
571 | someAsyncThing() { argument1, argument2, argument3 in
572 | print(argument3)
573 | }
574 |
575 | // Правильно
576 | someAsyncThing() { _, _, argument3 in
577 | print(argument3)
578 | }
579 | ```
580 |
581 |
582 |
583 | * # **Однострочные замыкания должны содержать по одному пробелу до и после каждой скобки, за исключением пробела между закрывающей скобкой и следующим оператором.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#closure-spacing)
584 |
585 |
586 |
587 | ```swift
588 | // Неправильно
589 | let evenSquares = numbers.filter {$0 % 2 == 0}.map { $0 * $0 }
590 |
591 | // Правильно
592 | let evenSquares = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }
593 | ```
594 |
595 |
596 |
597 | ### Операторы
598 |
599 | * # **Инфиксные операторы должны отделятся одним пробелом с каждой стороны.** Предпочитайте скобки, чтобы визуально группировать выражения с большим количеством операторов, а не изменять ширину пробелов. Это правило не относится к операторам диапазона (например, `1...3`) и к префиксным или постфиксным операторам (например, `guest?` или `-1`). [](https://github.com/realm/SwiftLint/blob/master/Rules.md#operator-usage-whitespace)
600 |
601 |
602 |
603 | ```swift
604 | // Неправильно
605 | let capacity = 1+2
606 | let capacity = currentCapacity ?? 0
607 | let mask = (UIAccessibilityTraitButton|UIAccessibilityTraitSelected)
608 | let capacity=newCapacity
609 | let latitude = region.center.latitude - region.span.latitudeDelta/2.0
610 |
611 | // Правильно
612 | let capacity = 1 + 2
613 | let capacity = currentCapacity ?? 0
614 | let mask = (UIAccessibilityTraitButton | UIAccessibilityTraitSelected)
615 | let capacity = newCapacity
616 | let latitude = region.center.latitude - (region.span.latitudeDelta / 2.0)
617 | ```
618 |
619 |
620 |
621 | ## Паттерны
622 |
623 | * # **Инициализируйте свойства в `init` где это возможно, а не используйте форс-анвраппинг.** Заметным исключением является UIViewController и его `view` свойство. [](https://github.com/realm/SwiftLint/blob/master/Rules.md#implicitly-unwrapped-optional)
624 |
625 |
626 |
627 | ```swift
628 | // Неправильно
629 | class MyClass: NSObject {
630 |
631 | var someValue: Int!
632 |
633 | init() {
634 | super.init()
635 | someValue = 5
636 | }
637 |
638 | }
639 |
640 | // Правильно
641 | class MyClass: NSObject {
642 |
643 | var someValue: Int
644 |
645 | init() {
646 | someValue = 0
647 | super.init()
648 | }
649 |
650 | }
651 | ```
652 |
653 |
654 |
655 | * # **Избегайте выполнение любой значимой или времязатратной работы в `init()`.** Избегайте таких действий, как открытие соединения с базой данных, выполнение запросов в сеть, чтение большого объема данных с диска и т.п. Создайте метод вроде `start()` если вам нужно чтобы эти действия были выполнены до того как объект будет готов к использованию.
656 |
657 | * # **Выносите сложные наблюдатели свойств в методы.** Это уменьшает вложенность, отделяет сайд-эффекты от объявления и делает явным использование неявно передаваемых параметров, таких как `oldValue`.
658 |
659 |
660 |
661 | ```swift
662 | // Неправильно
663 | class TextField {
664 | var text: String? {
665 | didSet {
666 | guard oldValue != text else {
667 | return
668 | }
669 |
670 | // Куча побочных эффектов связанных с текстом
671 | }
672 | }
673 | }
674 |
675 | // Правильно
676 | class TextField {
677 | var text: String? {
678 | didSet { updateText(from: oldValue) }
679 | }
680 |
681 | private func updateText(from oldValue: String?) {
682 | guard oldValue != text else {
683 | return
684 | }
685 |
686 | // Куча побочных эффектов связанных с текстом
687 | }
688 | }
689 | ```
690 |
691 |
692 |
693 | * # **Выносите сложные определения замыканий в методы**. Это уменьшает вложенность и сложность использования weak-self в блоках. Если необходимо сослаться на self в вызове замыкания, используйте `guard`, чтобы развернуть self на время вызова.
694 |
695 |
696 |
697 | ```swift
698 | // Неправильно
699 | class MyClass {
700 |
701 | func request(completion: () -> Void) {
702 | API.request { [weak self] response in
703 | if let strongSelf = self {
704 | // Processing and side effects
705 | }
706 | completion()
707 | }
708 | }
709 | }
710 |
711 | // Правильно
712 | class MyClass {
713 |
714 | func request(completion: () -> Void) {
715 | API.request { [weak self] response in
716 | guard let strongSelf = self else {
717 | return
718 | }
719 | strongSelf.doSomething(strongSelf.property)
720 | completion()
721 | }
722 | }
723 |
724 | func doSomething(nonOptionalParameter: SomeClass) {
725 | // Processing and side effects
726 | }
727 | }
728 | ```
729 |
730 |
731 |
732 | * # **Используйте `guard` в начале скоупа.**
733 |
734 |
735 |
736 | #### Почему?
737 | Проще рассуждать о блоке кода, когда все операторы `guard` сгруппированы вверху, а не смешаны с бизнес-логикой.
738 |
739 |
740 |
741 | * # **Контроль доступа должен быть максимально строгим.** Предпочитайте использование `public` вместо `open` и `private` вместо `fileprivate` пока вам не понадобится это поведение.
742 |
743 |
744 |
745 | ```swift
746 | // Неправильно
747 | final class ViewController {
748 |
749 | @IBOutlet weak var tableView: UITableView!
750 |
751 | var models: [Model] = []
752 |
753 | func reload() {
754 | // ...
755 | }
756 |
757 | }
758 |
759 | // Правильно
760 | final class ViewController {
761 |
762 | @IBOutlet private weak var tableView: UITableView!
763 |
764 | private var models: [Model] = []
765 |
766 | private func reload() {
767 | // ...
768 | }
769 |
770 | }
771 | ```
772 |
773 |
774 |
775 | * # **Избегайте глобальных функций где это возможно.** Предпочитайте методы в определениях типов.
776 |
777 |
778 |
779 | ```swift
780 | // Неправильно
781 | func age(of person, bornAt timeInterval) -> Int {
782 | // ...
783 | }
784 |
785 | func jump(person: Person) {
786 | // ...
787 | }
788 |
789 | // Правильно
790 | class Person {
791 | var bornAt: TimeInterval
792 |
793 | var age: Int {
794 | // ...
795 | }
796 |
797 | func jump() {
798 | // ...
799 | }
800 | }
801 | ```
802 |
803 |
804 |
805 | * # **Предпочитайте выделять константы в закрытый enum.** Если константы должны быть открыты, сделайте их статичными внутри определения класса.
806 |
807 |
808 |
809 | ```swift
810 | public class MyClass {
811 |
812 | private enum Constants {
813 | static let privateValue = "private"
814 | }
815 |
816 | public static let publicValue = "public"
817 |
818 | func doSomething() {
819 | print(Constants.privateValue)
820 | print(MyClass.publicValue)
821 | }
822 | }
823 | ```
824 |
825 |
826 |
827 | * # **Используйте `enum` без case для организации `public` или `internal` констант и функций в пространства имен.** Избегайте создания глобальных констант или функций. Не стесняйтесь вкладывать пространства имен, где это добавляет ясности.
828 |
829 |
830 |
831 | #### Почему?
832 | `enum`-ы без case хорошо работают как пространства имен так как они не могут быть созданы, что соответствует их назначению.
833 |
834 | ```swift
835 | enum Environment {
836 |
837 | enum Earth {
838 | static let gravity = 9.8
839 | }
840 |
841 | enum Moon {
842 | static let gravity = 1.6
843 | }
844 | }
845 | ```
846 |
847 |
848 |
849 | * # **Используйте неизменяемые значения где это возможно.** Используйте `map` и `compactMap` вместо добавления в новую коллекцию. Используйте `filter` вмеcто удаления элементов из изменяемой коллекции.
850 |
851 |
852 |
853 | #### Почему?
854 | Изменяемые свойства увеличивают сложность, поэтому старайтесь держать их в максимально узкой области.
855 |
856 | ```swift
857 | // Неправильно
858 | var results = [SomeType]()
859 | for element in input {
860 | let result = transform(element)
861 | results.append(result)
862 | }
863 |
864 | // Правильно
865 | let results = input.map { transform($0) }
866 | ```
867 |
868 | ```swift
869 | // Неправильно
870 | var results = [SomeType]()
871 | for element in input {
872 | if let result = transformThatReturnsAnOptional(element) {
873 | results.append(result)
874 | }
875 | }
876 |
877 | // Правильно
878 | let results = input.compactMap { transformThatReturnsAnOptional($0) }
879 | ```
880 |
881 |
882 |
883 | * # **Классы должны быть `final`, если другого не требует логика.**
884 |
885 |
886 |
887 | #### Почему?
888 | Если класс должен быть переопределен, автор должен указать эту функциональность, опуская ключевое слово `final`.
889 |
890 | ```swift
891 | // Неправильно
892 | class SettingsRepository {
893 | // ...
894 | }
895 |
896 | // Правильно
897 | final class SettingsRepository {
898 | // ...
899 | }
900 | ```
901 |
902 |
903 |
904 | * # **Никогда не используйте `default` case в `switch`.**
905 |
906 |
907 |
908 | #### Почему?
909 | Перечисление каждого case требует, чтобы разработчики и ревьюеры учитывали правильность каждого оператора switch при добавлении новых case.
910 |
911 | ```swift
912 | // Неправильно
913 | switch anEnum {
914 | case .a:
915 | // Do something
916 | default:
917 | // Do something else.
918 | }
919 |
920 | // Правильно
921 | switch anEnum {
922 | case .a:
923 | // Do something
924 | case .b, .c:
925 | // Do something else.
926 | }
927 | ```
928 |
929 |
930 |
931 | * # **Проверьте значение nil вместо использования разворачивания, если значение не требуется.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#unused-optional-binding)
932 |
933 | ## Организация файлов
934 |
935 | * # **Сортируйте импорты по алфавиту и ставьте их после комментариев в заголовке файла.** Если одна из библиотек уже импортирует какие-то библиотеки необходимые в вашем модуле - их импорты можно опустить. [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#sortedImports)
936 |
937 |
938 |
939 | #### Почему?
940 | Стандартный метод организации помогает инженерам быстрее определить, от каких модулей зависит файл.
941 |
942 | ```swift
943 | // Неправильно
944 |
945 | // Copyright © 2018 Surf. All rights reserved.
946 | //
947 | import DLSPrimitives
948 | import Constellation
949 | import Epoxy
950 |
951 | import UIKit
952 | import Foundation
953 |
954 | // Правильно
955 |
956 | // Copyright © 2018 Surf. All rights reserved.
957 | //
958 |
959 | import Constellation
960 | import DLSPrimitives
961 | import Epoxy
962 | import UIKit
963 | ```
964 |
965 |
966 |
967 | _Исключение: `@testable import` должны быть сгурппированы после обычных import и разделены пустой строкой._
968 |
969 |
970 |
971 | ```swift
972 | // Неправильно
973 |
974 | // Copyright © 2018 Surf. All rights reserved.
975 | //
976 |
977 | import DLSPrimitives
978 | @testable import Epoxy
979 | import Foundation
980 | import Nimble
981 | import Quick
982 |
983 | // Правильно
984 |
985 | // Copyright © 2018 Surf. All rights reserved.
986 | //
987 |
988 | import DLSPrimitives
989 | import Foundation
990 | import Nimble
991 | import Quick
992 |
993 | @testable import Epoxy
994 | ```
995 |
996 |
997 |
998 | * # **Ограничьте пустые вертикальные пробелы одной строкой.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#vertical-whitespace)
999 |
1000 | * # **Файлы должны заканчиваться новой строкой.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#trailing-newline)
1001 |
1002 | ## Совместимость с Objective-C
1003 |
1004 | * # **Старайтесь избегать наследования от NSObject.** Если ваш код должен быть использован каким-нибудь Objective-C кодом оберните его чтобы предоставить необходимую функциональность. Используйте `@objc` для отдельных функций и переменных вместо предоставления всего API класса при помощи `@objcMembers`.
1005 |
1006 |
1007 |
1008 | ```swift
1009 | class PriceBreakdownViewController {
1010 |
1011 | private let acceptButton = UIButton()
1012 |
1013 | private func setUpAcceptButton() {
1014 | acceptButton.addTarget(
1015 | self,
1016 | action: #selector(didTapAcceptButton),
1017 | forControlEvents: .TouchUpInside)
1018 | }
1019 |
1020 | @objc
1021 | private func didTapAcceptButton() {
1022 | // ...
1023 | }
1024 | }
1025 | ```
1026 |
1027 |
1028 |
--------------------------------------------------------------------------------
/resources/surf.swiftformat:
--------------------------------------------------------------------------------
1 | # options
2 | --self init-only # redundantSelf
3 | --importgrouping testable-bottom # sortedImports
4 | --trimwhitespace always # trailingSpace
5 |
6 | # rules
7 | --rules redundantParens,redundantSelf,sortedImports,trailingSpace
--------------------------------------------------------------------------------
/resources/swiftlint.yml:
--------------------------------------------------------------------------------
1 | whitelist_rules:
2 | - attributes
3 | - class_delegate_protocol
4 | - closing_brace
5 | - closure_end_indentation
6 | - closure_parameter_position
7 | - closure_spacing
8 | - collection_alignment
9 | - colon
10 | - comma
11 | - conditional_returns_on_newline
12 | - control_statement
13 | - convenience_type
14 | - custom_rules
15 | - cyclomatic_complexity
16 | - discouraged_optional_boolean
17 | - duplicate_imports
18 | - empty_count
19 | - empty_parameters
20 | - empty_parentheses_with_trailing_closure
21 | - empty_string
22 | - explicit_init
23 | - file_length
24 | - first_where
25 | - force_cast
26 | - force_try
27 | - force_unwrapping
28 | - function_parameter_count
29 | - implicit_getter
30 | - implicitly_unwrapped_optional
31 | - inert_defer
32 | - large_tuple
33 | - last_where
34 | - leading_whitespace
35 | - legacy_cggeometry_functions
36 | - legacy_constant
37 | - legacy_constructor
38 | - legacy_hashing
39 | - legacy_nsgeometry_functions
40 | - line_length
41 | - literal_expression_end_indentation
42 | - mark
43 | - multiline_arguments
44 | - multiline_literal_brackets
45 | - notification_center_detachment
46 | - opening_brace
47 | - operator_usage_whitespace
48 | - redundant_discardable_let
49 | - redundant_optional_initialization
50 | - redundant_nil_coalescing
51 | - redundant_void_return
52 | - return_arrow_whitespace
53 | - shorthand_operator
54 | - statement_position
55 | - syntactic_sugar
56 | - todo
57 | - toggle_bool
58 | - trailing_comma
59 | - trailing_newline
60 | - trailing_semicolon
61 | - trailing_whitespace
62 | - unused_import
63 | - unused_optional_binding
64 | - unused_setter_value
65 | - vertical_whitespace
66 | - void_return
67 | - weak_delegate
68 |
69 | disabled_rules: # rule identifiers to exclude from running
70 |
71 | opt_in_rules: # some rules are only opt-in
72 |
73 | excluded: # paths to ignore during linting. Takes precedence over `included`.
74 | - fastlane
75 | - Pods
76 | - .bundle
77 |
78 | custom_rules:
79 | image_name_initialization: # Disable UIImage init from name
80 | included: ".*.swift"
81 | name: "Image initialization"
82 | regex: 'UIImage\(named:[^)]+\)'
83 | message: "Use UIImage(assetName: ) instead"
84 | severity: error
85 |
86 | open_iboutlets:
87 | included: ".*.swift"
88 | name: "IBOutlet opening"
89 | regex: "@IBOutlet ?(weak){0,1} var"
90 | message: "IBOutlet should be private or fileprivate"
91 | severity: error
92 |
93 | open_ibaction:
94 | included: ".*.swift"
95 | name: "IBAction opening"
96 | regex: "@IBAction func"
97 | message: "IBAction should be private or fileprivate"
98 | severity: error
99 |
100 | line_length: 120
101 |
102 | file_length:
103 | warning: 500
104 | error: 1200
105 |
106 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit)
107 |
--------------------------------------------------------------------------------
/resources/xcode_settings.bash:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | defaults write com.apple.dt.Xcode DVTTextEditorTrimTrailingWhitespace -bool YES
6 | defaults write com.apple.dt.Xcode DVTTextEditorTrimWhitespaceOnlyLines -bool YES
7 |
8 | defaults write com.apple.dt.Xcode DVTTextIndentTabWidth -int 4
9 | defaults write com.apple.dt.Xcode DVTTextIndentWidth -int 4
10 |
11 | defaults write com.apple.dt.Xcode DVTTextPageGuideLocation -int 120
--------------------------------------------------------------------------------