image?

Думаю, все знают, как бывает непросто поддерживать соблюдение code style и соглашений в iOS-проекте. Сегодня поговорим о том, как автоматизировать этот процесс с помощью утилиты SwiftLint.

SwiftLint — это утилита от разработчиков Realm для автоматической проверки Swift-кода. Утилита содержит набор правил, основанных на GitHub's Swift Style Guide и здравом смысле. Разумеется можно добавлять свои правила. SwiftLint поддерживает интеграцию с Xcode, Appcode, Atom.

Установка и настройка
Скачиваем и устанавливаем самый свежий релиз из репозитория.

Или устанавливаем через терминал:

$ brew update
$ brew install swiftlint

Обновить утилиту можно так:

$ brew update
$ brew upgrade swiftlint

Переключиться на другую версию:

$ brew switch swiftlint <номер версии>

Узнать текущую версию:

swiftlint version

Заходим в настройки проекта, Build Phases и добавляем в секцию Run Script:

if which swiftlint >/dev/null; then
  swiftlint
else
  echo "error: SwiftLint does not exist, download it from https://github.com/realm/SwiftLint"
  exit 1
fi

Совет: крайне рекомендуется использовать команду «exit 1» — это гарантирует установку SwiftLint всеми членами команды.

Первый запуск
Теперь, когда SwiftLint установлен, в конце каждой сборки проекта будет происходить проверка кода. После первого запуска вы вероятнее всего увидите что-то типа:



Не стоит сильно переживать, большинство ошибок и предупреждений достаточно просты и легко исправимы. Не спешите исправлять их вручную, поскольку SwiftLint имеет замечательную функцию автокоррекции, она достаточно безопасна. Для её использования в терминале заходим в каталог проекта и выполняем команду:

swiftlint autocorrect

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

Правила
Список предустановленных правил можно увидеть, выполнив команду:

swiftlint rules



У каждого правила есть описание и набор параметров:

  • opt-in — является ли правило опциональным (отключено по умолчанию);
  • correctable — возможно ли настраивать правило;
  • enabled in your config — включено ли в проекте;
  • configuration — параметры.

Также можно посмотреть исходники правил для большего понимания.

Отдельно хочется выделить несколько очень полезных правил:

  • сyclomatic_сomplexity — правило ограничивающее цикломатическую сложность;
  • nesting — уровень вложенности классов и функций;
  • file_lenght — количество строк в файле;
  • function_body_lenght — количество строк в функции;
  • force_try/cast/unwrapping — наличие операций, потенциально приводящих к крэшу;
  • weak_delegate — проверка того, что делегат держится слабой ссылкой;
  • missing_docs — написаны ли комментарии к публичным функциям/свойствам.

Конфигурация
Вероятнее всего вам потребуется отключить, настроить или добавить какие-либо правила. Для этого в корне проекта нужно создать файл .swiftlint.yml.

Доступные параметры конфигурации:

Список правил, которые необходимо отключить:

disabled_rules:
  - colon
  - comma
  - control_statement

Список опциональных правил, которые необходимо включить (по умолчанию отключены):

opt_in_rules:
  - empty_count
  - missing_docs

Исключить подкаталоги или файлы:

excluded: 
  - Carthage
  - Pods
  - Source/ExcludedFolder
  - Source/ExcludedFile.swift

Включить подкаталоги или файлы (альтернатива excluded):

included: 
  - MyProject
  - MyProjectKeyboard
  - MyProjectTests

Параметры правил (доступные параметры можно найти в списке правил):

file_length:
  warning: 500
  error: 600

Тип отчета (доступные параметры: xcode, json, csv, checkstyle, junit):

reporter: xcode

Максимально допустимое количество предупреждений:

warning_threshold: 15

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

Вложенные конфигурации
Вы можете создавать несколько файлов конфигураций (.swiftlint.yml) для различных подкаталогов. SwiftLint будет автоматически использовать конфигурацию расположенную в папке с проверяемыми файлами. Параметры excluded и included для вложенных конфигураций будут игнорироваться.

Добавление своих правил
SwiftLint позволяет добавлять свои правила на основе регулярных выражений. Для этого необходимо в файле .swiftlint.yml добавить секцию custom_rules. У правила можно указать следующие параметры:

  • identifer — идентификатор (обязательно);
  • regexp — условие нахождения (обязательно);
  • name — имя правила, отображается в тексте ошибки (необязательно);
  • message — описание ошибки (обязательно);
  • match_kinds — в каких сущностях искать вхождение, доступные значения (можно указывать несколько): comment, identifier, number, parameter, string, полный список доступен в документации (обязательно);
  • severity — тип: предупреждение (warning) или ошибка (error) (необязательно, по умолчанию — warning);
  • included — какие файлы необходимо проверять (необязательно

Правило, проверяющее именование с суффиксом -id. (например: userId — верно, userID — неверно):

custom_rules:
  id_suffix_naming:
    name: "Wrong name"
    regex: "(ID)"
    match_kinds:
      - comment
      - identifier
    message: "Use 'Id' instead 'ID'"
    severity: error



Отключение правил в коде
Если хотите отключить проверку в части кода, используйте следующую конструкцию:

// swiftlint:disable <rule1> [<rule2> <rule3>...]
.........
// swiftlint:enable <rule1> [<rule2> <rule3>...]

func printName() {
    // swiftlint:disable force_cast
    let name = loadName() as! String
    // swiftlint:enable force_cast
    print(name)
}

Скорость сборки проекта
Очевидно, что использование SwiftLint требует дополнительного времени при сборке проекта. Если скорость сборки заметно проседает, стоит задуматься о проверке только измененных файлов. Для этого нужно заменить скрипт проверки в Build Phases на:

Скрипт
	
count=0
for file_path in $(git ls-files -om --exclude-from=.gitignore | grep ".swift$"); do
    export SCRIPT_INPUT_FILE_$count=$file_path
    count=$((count + 1))
done
for file_path in $(git diff --cached --name-only | grep ".swift$"); do
    export SCRIPT_INPUT_FILE_$count=$file_path
    count=$((count + 1))
done
    export SCRIPT_INPUT_FILE_COUNT=$count
    swiftlint lint --use-script-input-files


Несколько примеров конфигураций SwiftLint в известных компаниях:

Kickstarter
disabled_rules:
  - function_parameter_count
  - nesting
  - variable_name
  - weak_delegate
  - trailing_comma
opt_in_rules:
  - empty_count
  - force_unwrapping
  - private_outlet
line_length: 110
type_body_length:
  warning: 300
  error: 400
excluded:
  - Carthage/
  - Frameworks/
  - Kickstarter-iOS.playground/
  - Kickstarter-tvOS.playground/
  - Library/Strings.swift
  - bin/strings.swift
reporter: "xcode"
custom_rules:
  localized_lensing:
    name: "Localized Lensing"
    regex: "\.~\s+Strings\s*\."
    message: "Capture calls to `Strings` functions using `%~ { _ in Strings... }`"
    severity: error


Firefox
disabled_rules: # rule identifiers to exclude from running
  - legacy_constructor
  - variable_name
  - legacy_cggeometry_functions
  - legacy_constant
  - todo
  - trailing_newline
  - empty_count
  - force_cast
  - type_name
  - function_body_length
  - missing_docs
  - conditional_binding_cascade
  - valid_docs
  - cyclomatic_complexity
  - type_body_length
  - function_parameter_count
  - force_try
  - control_statement
  - trailing_whitespace
  - leading_whitespace
  - operator_whitespace
  - file_length
  - mark
opt_in_rules: # some rules are only opt-in
  - closing_brace
  - opening_brace
  - return_arrow_whitespace
  - trailing_semicolon
  - statement_position
  # Find all the available rules by running:
  # swiftlint rules
included: # paths to include during linting. `--path` is ignored if present.
excluded: # paths to ignore during linting. Takes precedence over `included`.
  - Carthage
  - Pods
  - Source/ExcludedFolder
  - Source/ExcludedFile.swift
  - ThirdParty
  - FxA
  - FxAClient
  - build

# configurable rules can be customized from this configuration file
# binary rules can set their severity level
trailing_semicolon: error
empty_count: error
closing_brace: error
opening_brace: error
return_arrow_whitespace: error
statement_position: error
colon: error
comma: error

line_length: 1000

reporter: "json" # reporter type (xcode, json, csv, checkstyle)


Realm

included:
  - Realm/ObjectServerTests
  - RealmSwift
  - Realm/Swift
variable_name:
  min_length: # not possible to disable this partial rule, so set it to zero
    warning: 0
    error: 0
disabled_rules:
  - file_length
  - force_cast
  - force_try
  - function_body_length
  - todo
  - type_body_length
  - line_length
  - vertical_whitespace
  - syntactic_sugar


Тинькофф
warning_threshold: 15

excluded:
— Pods
— MBUITest

disabled_rules:
— trailing_whitespace

line_length:
warning: 150

function_parameter_count:
warning: 10
error: 15

file_length:
warning: 500

type_body_length:
warning: 400
error: 450

SwiftLint позволил нашей команде ios разработчиков тратить меньше времени на код-ревью, придерживаться единого code style и повысить качество кода. Если вы еще не используете SwiftLint в своем проекте, то обязательно попробуйте.

Полезные ссылки:

» Репозиторий SwiftLint
» Список правил SwiftLint
» Про проверку только измененных файлов
» GitHub's Swift Style Guide
» Tailor — альтернатива SwiftLint
Поделиться с друзьями
-->

Комментарии (12)


  1. johnfound
    12.01.2017 16:37
    -3

    Просто цитата:


    Стремление вновь и вновь упорядочивать предметы — возможный признак аутизма[1].


    1. gats
      12.01.2017 18:22
      +1

      Медицинские определения плохо ложатся на код


  1. RedRover
    12.01.2017 18:08

    Надеюсь, что SwiftLint более основан на здравом смысле чем на GitHub's Swift Style Guide, в последнем встречаются реально вредные советы, одно только Prefer structs over classes сколько способно создать проблем в более-менее большом проекте.


    1. DarkEld3r
      12.01.2017 18:51

      А можно разжевать для тех, кто далёк от Swift, чем этот совет плох? Насколько я вижу, структуры создаются на стеке, зато у классов есть наследование и прочие штуки. Соответственно, совет воспринял бы как "пока не упираетесь в ограничения структур — используйте их". Или в чём тут проблема?


      1. RedRover
        13.01.2017 09:41
        +1

        Классические структуры из С так себя и ведут, но swift структуры вещь гораздо более сложная. Если в общем то поведение структуры будет сильно зависеть от ее наполнения. Если структура содержит пару примитивов, то тут все хорошо и ожидаемо. Но если структура содержит ссылки на классы то начинается форменная вакханалия, например имеем структуру с X полями которые являются классами, и передаем такую структуру в метод. Был бы это класс мы бы получили вызов retain при входе и вызов release при выходе, но у нас ведь структура поэтому вызов retain/release будет сделан для КАЖДОГО поля. Не спасает дело и то что String в swift структура, на самом деле String будет структурой (в классическом понимание) если это константа объявленная в коде, если нет то String будет оболочкой над классом и соответсвенно помещение такой строки в структуру равноценно помещению туда класса. (похожая ситуация и со swift коллекциями)
        Дальше интересней, для структуры генерится больше бинарного и бит кода чем для класса (это связано именно с такой сложной реализацией) что увеличивает размер приложения что в свою очередь уже само по себе его замедляет.
        Т.е. по сути эффективное использования структур это то как они использовались в ObjC где структуры содержали 2 — 3 примитива (CGPoint, CGRect и тп)
        Но подобные стайлгайды говорят что надо использовать структуры везде где можно, т.к. они не вызывают retain cycle'ов передаются по значению и «защищают» от наследования. В результате на свет рождаются огромные формации с 5+ класс полей, это все накапливается в проекте, начинает подтормаживать дистрибьютив пухнет и когда становится ясно в чем дело менять все как правило уже либо поздно либо трудно.


  1. RomanVolkov
    12.01.2017 18:27

    А в скрипте можно как-то указать папку для анализа SwiftLint'ом? А то в случае с Cocoapods он анализирует и эти исходники тоже


    1. iVampir
      12.01.2017 18:28

      Добавь в файл конфигурации:

      excluded:
      — Pods


  1. Johan
    12.01.2017 19:11

    Можно устанавливать используя CocoaPods. Автоматизация, и, к тому же, контроль над версией.


  1. killov
    13.01.2017 06:37

    Как то не смог войти на телефоне (Nokia Lumia 1520) в приложение мобильного банка Тинькофф. В пароле, с которым входил через веб интерфейс и клиента под Андроид оказались недопустимые символы…
    Однако, статьи про разработку пишут.


    1. PavelGatilov
      13.01.2017 13:06

      И какое отношение имеют разработчики iOS приложения к софту Windows Phone?
      И при этом даже если они в проекте допустили 100 багов — это не делает их статьи априори плохими.


  1. Sashke
    13.01.2017 13:06
    +1

    КМК лучше внедрение линта делать следющим образом:
    Сначала выключаем вообще все правила и после этого включаем по 1 правилу, убеждаемся что нас это правило устраивает, исправляем все варнинги и дальше по кругу.
    Таким образом не будет огромного количества варнингов и ошибок, и самое главное вы будете понимать каждое правило досконально.


    1. Johan
      13.01.2017 15:30

      Вот, кстати, да. Такой подход, заодно, позволит качественно отревьюить изменения после каждого включённого правила.