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

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

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

Начать курс

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

1. Что это и зачем нужна

Detekt — это мощный инструмент статического анализа кода для языка Kotlin, созданный компанией Arturbosch и поддерживаемый сообществом. Он предназначен для автоматического обнаружения потенциальных проблем, "запахов кода" (code smells), нарушения стилевых соглашений и уязвимостей в проектах на Kotlin. Detekt работает на уровне абстрактного синтаксического дерева (AST), что позволяет ему анализировать код глубже, чем простые текстовые линтеры. Инструмент интегрируется с системами сборки Gradle, Maven и другими CI/CD инструментами, что делает его незаменимым для поддержания качества кода в командной разработке.

Зачем нужен Detekt? Во-первых, он автоматизирует проверку кода на соответствие стандартам Kotlin Coding Conventions, что особенно важно в больших проектах с участием нескольких разработчиков. Во-вторых, Detekt помогает выявлять потенциальные баги на ранних стадиях: например, неиспользуемые переменные, возможные NullPointerException, сложные условные конструкции и другие проблемы, которые могут привести к ошибкам в рантайме. В-третьих, инструмент способствует рефакторингу: он подсвечивает участки кода с высокой цикломатической сложностью, длинные функции, избыточные элвис-операторы и другие паттерны, которые ухудшают читаемость и поддерживаемость кода.

Detekt предоставляет более 150 встроенных правил, разбитых на категории: потенциальные ошибки (potential bugs), стиль кода (style), производительность (performance), сложность (complexity), исключения (exceptions) и другие. При этом инструмент полностью настраиваем: вы можете включать/отключать отдельные правила, менять их пороговые значения, а также писать собственные правила на Kotlin. Detekt поддерживает генерацию отчетов в форматах HTML, XML, SARIF и текстовом, что позволяет интегрировать его с любыми системами анализа кода.

2. Установка

Detekt устанавливается как плагин для системы сборки Gradle (рекомендуемый способ) или как отдельный CLI-инструмент. Ниже приведены точные команды для установки.

Установка через Gradle (плагин)

Добавьте в файл build.gradle.kts (или build.gradle):

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

// Настройка Detekt (опционально)
detekt {
    buildUponDefaultConfig = true // использовать стандартные правила + свои
    allRules = false // не включать все правила (только рекомендованные)
    config.setFrom("$projectDir/config/detekt/detekt.yml") // путь к конфигу
    baseline = file("$projectDir/config/detekt/baseline.xml") // baseline для игнорирования ошибок
    reports {
        html.required.set(true) // генерировать HTML-отчет
        xml.required.set(true) // генерировать XML-отчет
        txt.required.set(true) // генерировать текстовый отчет
    }
}

После добавления плагина выполните команду:

./gradlew detekt

Установка через CLI (Homebrew для macOS/Linux)

brew install detekt

Установка через SDKMAN

sdk install detekt

Установка через JAR (универсальный способ)

curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.6/detekt-cli-1.23.6-all.jar
alias detekt='java -jar detekt-cli-1.23.6-all.jar'

3. Быстрый старт — минимальный рабочий пример

Создайте простой Kotlin-файл src/main/kotlin/example.kt:

// example.kt
fun main() {
    val x = 1
    val y = 2
    if (x > 0) {
        println("Positive")
    } else {
        println("Non-positive")
    }
    println("Sum: ${x + y}")
}

Создайте конфигурационный файл config/detekt/detekt.yml (можно пустой, Detekt использует стандартные настройки):

# config/detekt/detekt.yml
build:
  maxIssues: 0

Запустите Detekt через Gradle:

./gradlew detekt

Вы увидите отчет в консоли. Если все хорошо, Detekt не найдет нарушений. Теперь измените код, чтобы Detekt нашел проблему — например, удалите использование переменной y:

fun main() {
    val x = 1
    val y = 2 // Unused variable
    if (x > 0) {
        println("Positive")
    }
}

Запустите Detekt снова — он выдаст предупреждение о неиспользуемой переменной y.

4. Основные методы/функции/классы

Detekt предоставляет множество классов и методов для настройки и запуска анализа. Ниже приведены 8 ключевых элементов API.

4.1. DetektFacade — основной класс для запуска анализа

Сигнатура:

class DetektFacade(
    settings: Settings,
    ruleSetProvider: List<RuleSetProvider>,
    config: Config
)

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

Пример:

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.FileProcessor
import io.gitlab.arturbosch.detekt.core.DetektFacade
import io.gitlab.arturbosch.detekt.core.config.ConfigFactory
import io.gitlab.arturbosch.detekt.core.rules.RuleSetProvider

fun analyzeCode(code: String) {
    val config = ConfigFactory.load("detekt.yml")
    val facade = DetektFacade(
        settings = Settings(),
        ruleSetProvider = listOf(RuleSetProvider()),
        config = config
    )
    val result = facade.run(listOf(FileProcessor(code)))
    println(result)
}

4.2. ConfigFactory — загрузка конфигурации

Сигнатура:

object ConfigFactory {
    fun load(configPath: String): Config
    fun load(configPath: String, fallbackConfig: Config): Config
}

Что делает: Загружает YAML-конфигурацию Detekt из файла или ресурсов. Позволяет указать fallback-конфигурацию (например, стандартную).

Пример:

import io.gitlab.arturbosch.detekt.core.config.ConfigFactory

fun loadCustomConfig(): Config {
    val config = ConfigFactory.load("custom_detekt.yml")
    val defaultConfig = ConfigFactory.load("default-detekt-config.yml")
    return ConfigFactory.load("custom_detekt.yml", defaultConfig)
}

4.3. RuleSetProvider — провайдер набора правил

Сигнатура:

interface RuleSetProvider {
    val ruleSetId: String
    fun getRuleSet(config: Config): RuleSet
}

Что делает: Интерфейс для создания кастомных наборов правил. Вы можете реализовать его, чтобы добавить свои правила анализа.

Пример:

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider

class MyRuleSetProvider : RuleSetProvider {
    override val ruleSetId: String = "my-rules"
    
    override fun getRuleSet(config: Config): RuleSet {
        return RuleSet(ruleSetId, listOf(MyCustomRule(config)))
    }
}

4.4. Rule — базовый класс для создания правил

Сигнатура:

abstract class Rule(
    config: Config,
    description: String = "",
    val severity: Severity = Severity.Warning
) : TokenVisitor

Что делает: Абстрактный класс, от которого наследуются все правила Detekt. Содержит методы для обхода AST и выдачи предупреждений.

Пример:

import io.gitlab.arturbosch.detekt.api.*
import org.jetbrains.kotlin.psi.KtNamedFunction

class TooManyParametersRule(config: Config) : Rule(config) {
    override val issue = Issue(
        id = "TooManyParameters",
        severity = Severity.Warning,
        description = "Функция содержит слишком много параметров",
        debt = Debt.FIVE_MINS
    )
    
    override fun visitFunction(function: KtNamedFunction) {
        if (function.valueParameters.size > 5) {
            report(CodeSmell(issue, Entity.from(function), "Функция имеет ${function.valueParameters.size} параметров"))
        }
    }
}

4.5. CodeSmell — представление найденной проблемы

Сигнатура:

data class CodeSmell(
    val issue: Issue,
    val entity: Entity,
    val message: String,
    val references: List<Entity> = emptyList(),
    val metrics: List<Metric> = emptyList()
)

Что делает: Класс, представляющий одну найденную проблему в коде. Содержит информацию о правиле, местоположении, сообщении и метриках.

Пример:

import io.gitlab.arturbosch.detekt.api.*

fun createSmell(issue: Issue, entity: Entity): CodeSmell {
    return CodeSmell(
        issue = issue,
        entity = entity,
        message = "Найдена потенциальная проблема",
        metrics = listOf(Metric("complexity", 15))
    )
}

4.6. Entity — информация о местоположении в коде

Сигнатура:

data class Entity(
    val name: String,
    val signature: String,
    val location: Location,
    val ktElement: KtElement? = null
)

Что делает: Содержит информацию о конкретном элементе кода: имя, сигнатуру, местоположение (файл, строка, колонка) и ссылку на элемент PSI.

Пример:

import io.gitlab.arturbosch.detekt.api.*

fun createEntity(element: KtElement): Entity {
    return Entity(
        name = element.name ?: "anonymous",
        signature = element.text,
        location = Location(
            source = SourceLocation(element.containingFile?.name ?: "unknown"),
            line = element.lineNumber ?: 0,
            column = element.columnNumber ?: 0
        ),
        ktElement = element
    )
}

4.7. DetektReport — генерация отчетов

Сигнатура:

interface DetektReport {
    val format: String
    fun render(result: Detektion): String
}

Что делает: Интерфейс для создания отчетов в различных форматах. Встроенные реализации: HtmlReport, XmlReport, TextReport, SarifReport.

Пример:

import io.gitlab.arturbosch.detekt.report.html.HtmlReport
import io.gitlab.arturbosch.detekt.api.Detektion

fun generateHtmlReport(result: Detektion): String {
    val htmlReport = HtmlReport()
    return htmlReport.render(result)
}

4.8. Detektion — результат анализа

Сигнатура:

interface Detektion {
    val findings: Map<String, List<CodeSmell>>
    val errors: List<String>
    val metrics: List<Metric>
    val runErrors: List<String>
}

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

Пример:

import io.gitlab.arturbosch.detekt.api.Detektion

fun printFindings(result: Detektion) {
    result.findings.forEach { (ruleSet, smells) ->
        println("Набор правил: $ruleSet")
        smells.forEach { smell ->
            println("  - ${smell.issue.id}: ${smell.message} (строка ${smell.entity.location.line})")
        }
    }
}

5. Реальный практический пример (100-200 строк кода)

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

Файл конфигурации config/detekt/detekt.yml:

# config/detekt/detekt.yml
build:
  maxIssues: 10

comments:
  active: true
  CommentOverPrivateFunction:
    active: true
  CommentOverPrivateProperty:
    active: true

complexity:
  active: true
  ComplexCondition:
    active: true
    threshold: 3
  LargeClass:
    active: true
    threshold: 200
  LongMethod:
    active: true
    threshold: 30
  TooManyFunctions:
    active: true
    threshold: 10

exceptions:
  active: true
  ExceptionRaisedInU                    

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