Detekt для Kotlin: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в Detekt: статический анализ кода для Kotlin

Detekt — это мощный инструмент статического анализа кода для языка программирования Kotlin. В отличие от компилятора, который проверяет синтаксическую корректность, Detekt анализирует структуру, стиль и потенциальные проблемы в вашем коде без его выполнения. Это позволяет выявлять "запахи кода" (code smells), нарушения лучших практик, потенциальные ошибки и проблемы с производительностью на ранних этапах разработки.

Зачем нужен Detekt? В первую очередь, для поддержания высокого качества кодовой базы. В больших проектах с множеством разработчиков стиль кода может различаться, что усложняет его чтение и поддержку. Detekt автоматизирует проверку соответствия кода заданным правилам, что делает код более единообразным, предсказуемым и менее подверженным ошибкам. Он интегрируется в процесс сборки (Gradle, Maven) и может запускаться автоматически при каждом коммите или сборке, предотвращая попадание проблемного кода в репозиторий.

Ключевые возможности Detekt включают в себя:

  • Обнаружение "запахов кода": избыточная сложность, длинные методы, большие классы.
  • Проверка стиля: соответствие соглашениям об именовании, форматирование.
  • Выявление потенциальных ошибок: неиспользуемые переменные, возможные исключения.
  • Анализ производительности: неэффективные конструкции, излишние аллокации.
  • Настраиваемость: вы можете включать/отключать правила, настраивать их параметры и даже писать свои собственные.

Detekt написан на Kotlin и является де-факто стандартом для статического анализа в экосистеме Kotlin. Он активно поддерживается сообществом и используется в таких крупных проектах, как Kotlin Compiler, Ktor и Exposed.

Установка Detekt

Detekt не устанавливается через pip install или npm install, так как это инструмент для Kotlin/JVM экосистемы. Основные способы установки и интеграции:

1. Gradle Plugin (рекомендуемый способ)

Добавьте плагин Detekt в ваш build.gradle.kts (или build.gradle):

// build.gradle.kts
plugins {
    id("io.gitlab.arturbosch.detekt") version "1.23.6" // Проверьте актуальную версию
}

// Настройка (опционально)
detekt {
    buildUponDefaultConfig = true // Брать за основу стандартный конфиг
    allRules = false // Не включать все правила (экспериментальные тоже)
    config.setFrom("$projectDir/config/detekt.yml") // Путь к вашему конфигу
    baseline = file("$projectDir/config/baseline.xml") // Базовый уровень для подавления старых ошибок
}

// Подключаем Detekt к проверке перед сборкой
tasks.check {
    dependsOn(tasks.detekt)
}

2. Maven Plugin

Для Maven добавьте плагин в pom.xml:

<plugin>
    <groupId>io.gitlab.arturbosch.detekt</groupId>
    <artifactId>detekt-maven-plugin</artifactId>
    <version>1.23.6</version>
    <configuration>
        <buildUponDefaultConfig>true</buildUponDefaultConfig>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

3. CLI (Command Line Interface)

Вы можете запустить Detekt из командной строки, скачав JAR-файл с GitHub Releases:

java -jar detekt-cli-1.23.6.jar --input /path/to/your/kotlin/source --config /path/to/config.yml --report xml:output.xml

Быстрый старт с примером кода

Рассмотрим простой пример. Допустим, у вас есть файл src/main/kotlin/com/example/UserService.kt:

package com.example

class UserService {
    fun processUsers(users: List<String>?) {
        if (users != null) {
            for (user in users) {
                if (user.length > 5) {
                    println("User $user has a long name")
                }
            }
        }
    }
}

Запустим Detekt с настройками по умолчанию (команда ./gradlew detekt). Результат может выглядеть так:

complexity: LongMethod - The method 'processUsers' is too long. Currently 7 lines, threshold is 5.
style: WildcardImport - No wildcard imports allowed.
potential-bugs: UnreachableCode - This code is unreachable.

Detekt нашел несколько проблем:

  • LongMethod: Метод слишком длинный (7 строк, порог 5).
  • WildcardImport: Импорт import com.example.* не разрешен.
  • UnreachableCode: Недостижимый код (в данном примере нет, но Detekt мог бы найти что-то подобное).

Исправим код, чтобы он соответствовал правилам Detekt:

package com.example

import com.example.User // Явный импорт

class UserService {
    fun processUsers(users: List<String>) {
        users.filter { it.length > 5 }
            .forEach { println("User $it has a long name") }
    }
}

Теперь код короче, использует функциональный стиль и не содержит нарушений.

Основные методы и функции Detekt

Detekt предоставляет множество правил, сгруппированных по категориям. Рассмотрим 10 ключевых правил (каждое правило — это по сути "метод" анализа):

1. LongMethod (Сложность)

Что делает: Проверяет, что длина метода не превышает заданный порог. Слишком длинные методы сложны для понимания и тестирования.

Параметры: threshold (int, по умолчанию 20) — максимальное количество строк в методе.

Пример:

// Нарушение: метод длиной 25 строк
fun processData() {
    // ... 25 строк кода
}

// Исправление: разбить на несколько маленьких методов
fun processData() {
    validateInput()
    transformData()
    saveResults()
}

2. LargeClass (Сложность)

Что делает: Проверяет, что класс не слишком большой. Большие классы нарушают принцип единственной ответственности.

Параметры: threshold (int, по умолчанию 600) — максимальное количество строк в классе.

Пример:

// Нарушение: класс на 800 строк
class DataManager {
    // ... 800 строк кода
}

// Исправление: разделить на несколько классов
class DataValidator { ... }
class DataTransformer { ... }
class DataRepository { ... }

3. NamingConventionViolation (Стиль)

Что делает: Проверяет соответствие имен соглашениям (CamelCase для классов, lowerCamelCase для функций и переменных).

Параметры: variablePattern, functionPattern, classPattern (regex).

Пример:

// Нарушение
class user_service { ... } // Должно быть UserService
val USER_NAME = "John" // Должно быть userName

// Исправление
class UserService { ... }
val userName = "John"

4. UnusedPrivateMember (Потенциальные ошибки)

Что делает: Находит приватные функции, свойства или классы, которые нигде не используются. Это "мертвый код".

Параметры: Нет.

Пример:

class Calculator {
    private fun unusedHelper() = 42 // Detekt найдет это
    fun add(a: Int, b: Int) = a + b
}

5. MagicNumber (Стиль)

Что делает: Предупреждает о "магических числах" — числовых литералах, которые не присвоены именованной константе.

Параметры: ignoreNumbers (list, по умолчанию [-1, 0, 1, 2]), ignoreHashCodeFunction (boolean).

Пример:

// Нарушение
fun calculateDiscount(price: Double) = price * 0.15

// Исправление
const val DISCOUNT_RATE = 0.15
fun calculateDiscount(price: Double) = price * DISCOUNT_RATE

6. ReturnCount (Сложность)

Что делает: Ограничивает количество операторов return в функции. Множественные точки выхода усложняют понимание.

Параметры: max (int, по умолчанию 2).

Пример:

// Нарушение: 3 return
fun getStatus(value: Int): String {
    if (value < 0) return "Negative"
    if (value == 0) return "Zero"
    return "Positive"
}

// Исправление: использовать when
fun getStatus(value: Int): String = when {
    value < 0 -> "Negative"
    value == 0 -> "Zero"
    else -> "Positive"
}

7. NestedBlockDepth (Сложность)

Что делает: Проверяет глубину вложенности блоков кода (if, for, while). Слишком глубокая вложенность делает код нечитаемым.

Параметры: threshold (int, по умолчанию 4).

Пример:

// Нарушение: глубина 5
fun process(matrix: List<List<Int>>) {
    for (row in matrix) { // 1
        for (cell in row) { // 2
            if (cell > 0) { // 3
                if (cell % 2 == 0) { // 4
                    println(cell) // 5
                }
            }
        }
    }
}

// Исправление: использовать filter, flatten и т.д.
fun process(matrix: List<List<Int>>) {
    matrix.flatten()
        .filter { it > 0 && it % 2 == 0 }
        .forEach { println(it) }
}

8. ThrowsCount (Потенциальные ошибки)

Что делает: Ограничивает количество исключений, которые может выбросить функция. Слишком много исключений усложняет обработку ошибок.

Параметры: max (int, по умолчанию 2).

Пример:

// Нарушение: 3 исключения
fun riskyOperation() {
    throw IllegalArgumentException()
    throw IllegalStateException()
    throw RuntimeException()
}

// Исправление: использовать sealed class для ошибок
sealed class OperationError
class InvalidInput : OperationError()
class InvalidState : OperationError()
fun riskyOperation(): OperationError? { ... }

9. SpreadOperator (Производительность)

Что делает: Предупреждает об использовании spread-оператора (*) в вызовах функций, так как это создает временный массив.

Параметры: Нет.

Пример:

// Нарушение (потенциально)
fun print                    

Рекомендаций