Статический анализ для Android проектов на Kotlin

У кого код в порядке, у того и мысли в порядке.

Народная мудрость

Код должен быть чистым и понятным — это известно всем программистам, хоть и не все его таким пишут. Существуют различные правила форматирования, именования, построения классов и функций, придумано множество архитектурных подходов и шаблонов проектирования. Всё это нужно для того, чтобы сделать код максимально простым и понятным. Компьютеру плевать на форматирование и понятные имена переменных — к моменту запуска код будет несколько раз перемолот разными компиляторами и оптимизаторами, пользователям тоже это всё не интересно. Код должен быть понятен нам — разработчикам. Для того, чтобы улучшить качество кода существует такой тип инструментов, как статические анализаторы.

Что такое?

Статический анализ, это проверка кода без его его компиляции и запуска(или без интерпретации для интерпретируемых языков). Существует целый ряд проблем, которые можно выявить на этом этапе. Намного проще и дешевле найти проблему ещё до коммита, чем столкнуться с ней позже, например, на code review.

Зачем?

Некоторые виды проблем, с которыми может помочь статический анализ:

  • Следование codestyle языка;
  • Проверка правописания;
  • Обнаружение code smells;
  • Обнаружение некоторых видов ошибок, от безобидных до ошибок производительности и безопасности;
  • Обнаружение неиспользуемых ресурсов и мёртвого кода;
  • Анализ кода — циклические зависимости, сложность, количество строк\классов\функций и т.д.

Не панацея

Как бы хорошо это всё ни звучало, статический анализ не решит всех возможных проблем. Код может быть ужасен во всех смыслах, но успешно проходить все проверки. Важно понимать, что статические анализаторы — просто дополнительный инструмент, помогающий сделать код немного лучше. Но, что важно, этот инструмент очень дешёвый — настраивается один раз или не требует настройки вообще, а проверки отнимают минимальное время.

Этап 1. Глаза программиста

Формально просмотр кода глазами можно назвать статическим анализом, так как код изучается, но не запускается. Только программист по-настоящему может оценить красоту и чистоту кода. Но в то же время человеческий мозг не идеален и может многое не заметить.

Этап 2. IDEA Inspections

Первый статический анализатор, с которым мы сталкиваемся, возможно, неосознанно, встроен прямо в IDE (IDEA, Android Studio) и работает постоянно, если специально не выключен. Когда IDEA ругается на unreachable statement или говорит, что поле может быть private вместо public, или сообщает о вызове несуществующего метода — это оно, та самая всплывающая лампочка.

Настройка

Набор инспекций очень большой, но многие выключены в целях оптимизации производительности. Просмотреть все инспекции и включить\выключить нужные можно в настройках — Settings/Editor/Inspections:

Каждой инспекции присвоен уровень строгости:

Рекомендую сразу включить инспекцию Kotlin/Style Issues/File is not formatted:

Теперь IDEA будет ругаться, если файл не отформатирован по выбранному codestyle. На всякий случай можно проверить сам codestyle в настройках Settings/Code Style/Kotlin, и выбрать Set From/Predefined Style/Kotlin Style Guide:

Кстати, не помешает изучить официальный документ Kotlin Coding Conventions.

В правом нижнем углу IDE можно найти иконку в виде человечка в шляпе:

Она даёт доступ к быстрой настройке уровня инспекций и отсюда же можно перейти к списку инспекций:

Форматирование

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

  1. Ctrl+Alt+O — оптимизирует импорты;
  2. Ctrl+Alt+L — форматирует файл.

Принцип работы с проблемами

Алгоритм решения проблем всегда один:

  1. Нажимаем F2(возможно, несколько раз), чтобы переместить курсор в проблемное место в коде;
  2. Нажимает Alt+Enter, чтобы посмотреть возможные решения;
  3. В открывшемся меню выбираем один из предложенных вариантов решения;
  4. Если подходящего варианта нет, решаем проблему самостоятельно.

Рассмотрим небольшой пример. Короткий фрагмент кода с несколькими видами проблем:

  • Неиспользуемые импорты;
  • Неотформатированный код;
  • Вызов несуществующей функции stopTheApp();
  • Неиспользуемая переменная hello;
  • Синтаксическая ошибка в слове inspections[z].

Последовательно устраняем все проблемы:

 

Ручной запуск инспекций

Инспекции можно прогнать вручную во всём проекте или в отдельном модуле. Это можно сделать через меню Analyze/Inspect Code или, как и любую команду, вызвать через глобальный поиск — Ctrl+Shift+A/Inspect Code:

После всех проверок выскочит панель Inspection Result со списком всех найденных проблем:

Кроме того можно запускать отдельные инспекции. Ctrl+Shift+A/Run Inspection By Name/Inspection Name

Проверка перед коммитом

Чтобы код с проблемами не попал в коммит лучше включать прогон инспекций, форматирование кода и оптимизацию импортов:

Игнорирование проблем

Статический анализатор можно заткнуть с помощью аннотации @Suppress:

Так мы заявляем «я знаю о проблеме, но делать с ней ничего не буду». В целом так делать не стоит, лучше честно решать найденные проблемы, но в крайне редких случаях это может быть полезно.

Ссылка на документацию

Этап 3. Android Lint

Отдельный Android-специфический инструмент, который работает в паре с IDEA Inspections. Проверяет код, gradle конфиги, xml файлы ресурсов. Список проверок находится в Settings/Editor/Inspections/Android. При запуске Inspect Code в Android проекте в результаты добавляется отдельный пункт про Android:

Тут всё точно так же, как и при работе c IDEA Inspections, но кроме этого Android Lint может работать, как отдельный инструмент, например, через gradle.

Запуск через Gradle

Существуют специальные gradle таски — lint и lintDebug. Ctrl+Ctrl/gradlew lint — после всех проверок будут сгенерированы два отчёта — html для чтения людьми и xml вариант, который может пригодиться для автоматической обработки. Найти эти отчёты можно по пути module_name/build/reports/. Html версия крайне полезна, если lint запускается без ide, например в ci/cd системе:

Настройка через Gradle

Настройки lintOptions прописываются в build.gradle уровня модуля в разделе android. Выглядеть это может примерно так:

android {
    //...

    lintOptions {
        // Отключение инспекций
        disable 'HardcodedText'

        // Включение инспекций
        enable 'RtlHardcoded', 'RtlCompat', 'RtlEnabled'

        // Прекращать проверку при обнаружении ошибки
        // Может быть полезно для ci/cd
        abortOnError false

        // Сообщения только об ошибках(errors), предупреждения(warnings) игнорируются
        ignoreWarnings true
    }

    // ...
}

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

Игнорирование проблем

Так же, как и в IDEA Inspection проблемы можно игнорировать. Для этого существует аннотация @SuppressLint.

Можно заблокировать как одну проблему:

@SuppressLint("ResourceType")

Так и несколько:

@SuppressLint("ResourceType", "HardcodedText")

Или даже вообще все:

@SuppressLint("ALL")

Повторюсь, что не стоит усердствовать с игнорированием проблем, лучше их исправлять. Нужно стремиться к такому состоянию, а вот такого избегать. Конечно, этого можно добиться игнорированием проблем, но это путь в никуда.

Также можно игнорировать проблемы в xml, для этого есть атрибут tools:ignore :

<EditText
    tools:ignore="HardcodedText"
    tools:text="Username" />

Аннотации в помощь

Android Lint во многом опирается на специальные аннотации, которые и мы можем использовать в своём коде. Эти аннотации находятся в библиотеке androidx.annotation

Приведу несколько наглядных примеров.

Если в параметр метода нужно принять не просто Int, а строковый ресурс, его можно пометить аннотацией @StringRes. Если будет передан просто Int, Lint будет ругаться:

fun takeStringRes(@StringRes resId: Int) {}

Для других типов ресурсов есть соответствующие аннотации: @DrawableRes, @DimenRes, @ColorRes, @IntegerRes и т.д.

Если функция должна принимать число в рамках диапазона, его можно определить аннотациями @IntRange и @FloatRange:

fun takeRange(@IntRange(from = 0, to = 255) value: Int) {}

Если при переопределении метода наследуемого класса необходимо вызвать super, нужно прописать аннотацию @CallSuper, и тогда Lint заставит его вызвать. Такое поведение можно увидеть у методов жизненного цикла Activity и Fragment.

@CallSuper
fun someMethod() { }

Отдельно хочу выделить аннотации @IntDef и @StringDef, которые позволяют создавать что-то вроде подтипов. Работает это следующим образом:

Кроме того, что Lint будет ругаться, если передать в метод просто Int, IDE в автодополнении сразу предложит эти константы.

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

Это ещё не всё

Это не все возможности Android Lint. Я рассказал только о тех, которые, на мой взгляд, наиболее полезны. Можно ещё, например, хранить настройки во внешнем xml файле или даже писать собственные инспекции. Для более глубокого понимания инструмента рекомендую ознакомиться с документацией.

Этап 4. Стронние статические анализаторы

ktlint

Этот инструмент следит за форматированием кода. Опирается он на официальный kotlin codestyle и следует нехитрому набору правил. Может работать, как отдельный standalone инструмент, но для Android проектов удобней воспользоваться gradle плагином.

Подключить ktlint к проекту можно следующим образом:

В build.gradle уровня проекта в блок buildscript нужно прописать репозиторий и зависимость:

buildscript {

    repositories {
        // ...
        maven { url "https://plugins.gradle.org/m2/" }
    }

    dependencies {
        // ...
        classpath "org.jlleitschuh.gradle:ktlint-gradle:8.2.0"
    }
}

В build.gradle уровня модуля (например :app) нужно применить плагин и прописать настройки:

apply plugin: "org.jlleitschuh.gradle.ktlint"

// ...

ktlint {
    version = "0.33.0"
    android = true
}

После этого появятся новые gradle таски. Таск ktlintCheck (Ctrl+Ctrl/gradlew app:ktlintCheck) проверяет форматирование кода, сообщает о найденных ошибках и генерирует файл с отчётом:

По клику на каждую ошибку можно перейти в файл и всё исправить.

Ещё один полезный таск — ktlintFormat автоматически форматирует все файлы в соответствии с правилами, но может обработать не все ситуации и часто нужно корректировать код руками.

В общем то, это и всё. Инструмент довольно простой. Конечно, есть множество настроек и возможность добавлять свои правила, но лично меня стандартное поведение устраивает и, думаю, для большинства этого будет достаточно, особенно поначалу.

detekt

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

Подключение через gradle также простое. В build.gradle уровня проекта:

buildscript {

    repositories {
        // ...
       jcenter()
    }

    dependencies {
        // ...
        classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.0.0-RC16"
    }
}

В build gradle уровня модуля нужно применить плагин:

apply plugin: "io.gitlab.arturbosch.detekt"

И произвести настройку. Например, включить xml и html репорты:

detekt {
    reports {
        xml {
            enabled = true
            destination = file("build/reports/detekt.xml")
        }
        html {
            enabled = true
            destination = file("build/reports/detekt.html")
        }
    }
}

После запуска таска detekt (Ctrl+Ctrl/gradlew app:detekt) будет выведен список найденных проблем, разделённых на типы:

А также небольшой отчёт:

По умолчанию допускается максимум 10 ошибок. То есть, если detekt найдёт в коде 10 проблем или меньше, то всё будет в порядке, но если ошибок будет больше, сборка упадёт.

Настроек у detekt очень много, но я не буду на них останавливаться. Если вам понадобится изменить поведение, всю информацию можно найти в официальной документации.

Другие инструменты

Других достойных статических анализаторов для Kotlin мне найти на удалось. Разве что SonarKotlin в составе плагина для IDE. Но он развивается очень вяло и вряд ли представляет собой что-то полезное.

Упомяну некоторые статические анализаторы для Java, которые можно использовать в Android проектах.

  • SonarCube — большой и мощный статический  анализатор. Есть бесплатная версия;
  • PVS-Studio — также очень мощный статический анализатор. С недавних пор поддерживает Java. Есть бесплатная версия, но только для проектов с открытым кодом;
  • Checkstyle — инструмент для проверки Java Codestyle Conventions;
  • SpotBugs — относительно новый анализатор, пришедший на замена старому FindBugs.

Чтиво

Слепо полагаться на автоматические инструменты не стоит. Нужно понимать, чем плохой код отличается от хорошего. Хочу порекомендовать книгу Clean Code. Существует также неплохой русский перевод. И конечно же, стоит хорошо знать code conventions для Java и Kotlin, а также для других языков, на которых вы пишете.

Заключение

Инструменты статического анализа позволяют сделать код качественней и поддерживать его в хорошем состоянии. Они очень быстрые и не требуют сложной настройки, поэтому грех ими не пользоваться. В следующей статье я расскажу, как всё это использовать в связке с CI/CD.

Пишите чистый код, любите чистый код, используйте статический анализ.

 

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *