Применение статического анализатора кода SwiftLint в iOS мобильных приложениях банка

    Олег Иванов, руководитель направления центра компетенций дистанционных каналов обслуживания



    Всем привет! Как и обещал в статье «Мобильный банк от МКБ: история развития», хочу сегодня поговорить о статических анализаторах кода и об опыте их применения в iOS мобильных приложениях банка.

    Давайте поставим цель, которую мы хотим достичь, используя этот инструментарий:

    • раннее выявление ошибок и недочётов;
    • Code Style (стандарт оформления кода — набор правил и соглашений, используемых при написании кода программы).

    Распространённым способом достижения поставленных нами целей является ручная проверка кода – Обзор кода (Code review).

    Проверяющий или группа проверяющих внимательно изучают проверяемый код, далее они выявляют ошибки или участки кода, которые могут стать ошибочными в будущем, и дают рекомендации по его улучшению через комментарии к коду, которые через некоторое (!) время будут проанализированы и исправлены разработчиком. Как мы видим, вырисовывается долгий и дорогостоящий процесс проверки кода. Плюс всегда есть человеческий фактор – некоторые ошибки могут быть просто-напросто пропущены проверяющими.

    Тут нам на помощь приходят статические анализаторы кода.

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

    Статические анализаторы кода – это не панацея и не замена ручного Сode review, но являются отличным инструментом, позволяющим снизить время на проведение проверки и оперативно найти шаблонизированные ошибки. Актуальность использования статических анализаторов со временем будет только расти.

    В качестве статического анализатора кода в своих iOS проектах мы используем SwiftLint.

    SwiftLint — это утилита автоматической проверки Swift-кода, работающая на этапе сборки проекта. Утилита содержит набор правил с возможностью дополнения этого набора своими custom правилами. Инструмент также применяется для соблюдения Code Style.

    Почему в приложении важно соблюдать Code Style?
    Когда вы один разработчик на проекте – всё просто: вы пишете в своём стиле и можете позволить себе код в подобном виде:



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



    Для знакомства с инструментом рекомендую почитать официальную документацию.

    Установка SwiftLint проста:

    1. Добавляем pod ‘SwiftLint' в Podfile проекта
    2. Добавляем новый «Run Script Phase» к проекту

      "${PODS_ROOT}/SwiftLint/swiftlint"
    3. Определяемся с набором правил SwiftLint и записываем их в конфигурационный файл .swiftlint.yml
    4. Добавивляем конфигурационный файл .swiftlint.yml в каталог, из которого будем запускать SwiftLint

    Если проект встраивания SwiftLint уже содержал код, придётся запастись терпением и планомерно исправить все рекомендации SwiftLint. Их он формирует через привычные error и warning отображения c исчерпывающими подсказками о рекомендации.



    Также в скобках он выведет имя правила, которое инициировало рекомендацию (operator_usage_whitespace).

    В данном случае это:

    Operator Usage Whitespace
    Операторы должны быть окружены одним пробелом.

    Правильный код:




    Код вызывающий рекомендацию:



    Каждое правило имеет набор атрибутов:

    Идентификатор: operator_usage_whitespace
    Включено по умолчанию: отключено
    Поддерживает автокоррекцию: да
    Вид: стиль
    Правило анализатора: Нет
    Минимальная версия компилятора Swift: 3.0.0
    Конфигурация по умолчанию: предупреждение

    Обращайте внимание на «Минимальная версия компилятора Swift» – соотносите использование правил с этим атрибутом и с настройками вашего проекта.

    Атрибут «Конфигурация по умолчанию» показывает, как правила с этим атрибутом будут восприниматься: или обычное предупреждение, или ошибка компиляции, как например, правило Force Cast (Конфигурация по умолчанию: ошибка)

    Все правила можно посмотреть в очень удобной и иллюстрированной документации.

    Ниже я представлю правила, которые мы выбрали в своей команде, можете использовать их как пример для создания своего проекта.

    Мы разделили все правила на функциональные и стилистические – да-да-да и один пробел внутри каждой фигурной скобки, и параметры закрытия должны быть в той же строке, что и открывающая скобка, и… :). Правила не расписываю, информацию о них вы легко найдёте, воспользовавшись ссылкой выше.

    Функциональные:

    — private_outlet
    — force_unwrapping
    — force_cast
    — force_try
    — strong_iboutlet
    — private_action
    — block_based_kvo
    — contains_over_first_not_nil
    — discarded_notification_center_observer
    — discouraged_direct_init
    — discouraged_object_literal
    — discouraged_optional_boolean
    — discouraged_optional_collection
    — duplicate_imports
    — dynamic_inline
    — empty_count
    — empty_parameters
    — empty_parentheses_with_trailing_closure
    — empty_string
    — explicit_enum_raw_value
    — function_default_parameter_at_end
    — generic_type_name
    — identical_operands
    — implicit_getter
    — is_disjoint
    — notification_center_detachment
    — nsobject_prefer_isequal
    — redundant_set_access_control
    — unused_capture_list

    Cтилистические:

    — unneeded_parentheses_in_closure_argument
    — let_var_whitespace
    — yoda_condition
    — colon
    — comma
    — closure_parameter_position
    — closure_spacing
    — collection_alignment
    — leading_whitespace
    — mark
    — opening_brace
    — operator_usage_whitespace
    — operator_whitespace
    — protocol_property_accessors_order
    — return_arrow_whitespace
    — switch_case_alignment
    — statement_position
    — trailing_comma
    — trailing_newline
    — unneeded_break_in_switch
    — custom_rules
    — closure_end_indentation
    — file_name_no_space
    — unowned_variable_capture
    — no_space_in_method_call
    — contains_over_filter_count
    — contains_over_filter_is_empty
    — contains_over_range_nil_comparison
    — duplicate_enum_cases
    — empty_collection_literal

    Также под цель работы SwiftLint мы выбрали только каталог с нашей кодовой базой, добавив соответствующую настройку в конфигурационный .swiftlint.yml файл:

    included:

    — <каталог кодовой базы>


    Мы создали своё правило для недопустимости использования функции print. print — достаточно тяжелая операция. Через конфигурационный .swiftlint.yml файл:

    custom_rules:
      disable_print:
        included: ".*\\.swift"
        name: "print usage"
        regex: "((\\bprint)|(Swift\\.print))\\s*\\("
        message: "Prefer os_log over print"
        severity: error

    Это правило побудило нас написать свою функцию логирования (с уровнем логирования). Данное логирование используется разработчиками для быстрой отладки, к примеру, логи с параметрами, телом запроса/ответа нужны для любого разбора ошибок, для разработки. Для Release уровень лога в нашем варианте .none. Остальные же использования функции print приведут к ошибке сборки проекта.

    func logApp(level: Constants.LogLevel, items: Any...) {
        if Constants.logLevel == .none {
            return
        }
        
        if level.rawValue <= Constants.logLevel.rawValue {
            // swiftlint:disable disable_print
            if let strings = items as? [String] {
                for string in strings {
                    print(string)
                }
            } else {
                print(items)
            }
            // swiftlunt:enable disable_print
        }

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

    // swiftlint:disable disable_print
    // swiftlunt:enable disable_print

    SwiftLint имеет возможность игнорирования собственных правил:

    // swiftlint:disable <rule1 [rule2 rule3…]>
    <код, который игнорируется SwiftLint для правил rule1 [rule2 rule3…]>
    // swiftlunt:enable <rule1 [rule2 rule3…]>

    Или

    // swiftlint:disable all
    <код, который игнорируется SwiftLint для всех правил>
    // swiftlint:enable all

    Используйте эту возможность, полностью отдавая себе отчёт, что это необходимо!

    В заключении статьи отмечу: применение SwiftLint в нашей команде снизило стоимость ручного Code review на 20%. Теперь наши проверяющие не обращают внимание на типичные ошибки (Force Cast и др.), Code Style и могут в полной мере сосредоточиться на проверке нового кода. Что значительно повысило эффективность работы команды в целом (не нужно исправлять подобные ошибки, проверяющие – это квалифицированные сотрудники, чьё время очень важно).

    Всё! Теперь SwiftLint навсегда с вами :)

    Комментарии 3

      0
      1. Не обязательно включать очередной pod в проект, достаточно установить SwiftLint локально
      2. Пользуйтесь debugPrint вместо print, если он так нужен
      3. Большой кусок кода во viewDidLoad на скриншоте так себе code style — лучше разбить на несколько приватных функций по функционалу

      p.s.
      Про SwiftLint было уже прилично статей на Хабре. Не увидел в статье других «статических анализаторов кода и опыте их применения», как написано в заголовке
        0
        Добавлю к вашему замечанию в пункте №1: можно послушать подход ребята из Badoo к установке SwiftLint
          0
          Спасибо за комментарии. Название статьи должно быть только про SwiftLint — спасибо, что обратили внимание — поправил.

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

        Самое читаемое