DEV Community

Rodrigo Sicarelli
Rodrigo Sicarelli

Posted on • Edited on

Android Plataforma - Parte 15: Cuidando do código com Detekt, Klint e Spotless

🌱 Branch: 15/enhancing-code-quality

🔗 Repositório: github.com/rsicarelli/kotlin-gradle-android-platform

⬅️ Artigo Anterior: Parte 14: Aderindo a funcionalidades experimentais do compilador do Kotlin

➡️ Próximo Artigo: Parte 16: Considerações finais


No último artigo, abordamos a capacidade de nossa plataforma aderir a funcionalidades experimentais em diferentes módulos.

Agora, vamos explorar a garantia da qualidade do código através da integração de plugins.


Por que focar em automatizar verificações do código?

Quando se trabalha em equipe, é vital ter padrões de estilo e nomenclatura para manter a consistência. Estabelecer um padrão sólido ajuda a reduzir a sobrecarga de decisões e facilita a colaboração.

Pense assim: ao se juntar a uma orquestra, seguimos a pessoa contudora que dita ritmo da música. É o mesmo com nosso módulos; seguimos padrões preestabelecidos e acordados pelo time de forma automatizada.

Essa prática é especialmente útil para quando uma pessoa nova entra no time, além de que os acordos fiquem documentados e codificados, abertos para colaboração.

Incluíndo análise de código estático com Detekt

O Detekt é talvez a ferramenta mais famosa em Kotlin para analisar o código e garantir que algumas práticas são aplicadas.

Não iremos focar muito nas suas funcionalidades, vamos direto pra implementação

Passo a passo

1 - Vamos começar declarando o detekt no nossos libs.versions.toml:

[versions] detekt = "1.23.1" detektCompose = "0.2.3" [libraries] gradlePlugin-detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } detektRules-compose = { module = "io.nlopez.compose.rules:detekt", version.ref = "detektCompose" } detektRules-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } detektRules-libraries = { module = "io.gitlab.arturbosch.detekt:detekt-rules-libraries", version.ref = "detekt" } [plugins] arturbosch-detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } 
Enter fullscreen mode Exit fullscreen mode

2 - Sincronize o projeto. Navegue até build-logic/build.gradle.kts e vamos compilar a dependencia do detekt na nossa plataforma:

dependencies { compileOnly(libs.gradlePlugin.android) compileOnly(libs.gradlePlugin.kotlin) compileOnly(libs.gradlePlugin.detekt) } 
Enter fullscreen mode Exit fullscreen mode

3 - Sincronize o projeto. Agora, vamos declarar nossa DSL do DetektOptions.

Crie um arquivo DetektOptions em build-logic/src/../options

data class DetektOptions( val parallel: Boolean, val buildUponDefaultConfig: Boolean, val configFileNames: List<String>, val includes: List<String>, val excludes: List<String> ) class DetektOptionsBuilder { var parallel: Boolean = true var configFiles: List<String> = listOf(".detekt.yml, .detekt-compose.yml") var buildUponDefaultConfig: Boolean = true var includes: List<String> = listOf("**/*.kt", "**/*.kts") var excludes: List<String> = listOf(".*/resources/.*", ".*/build/.*") internal fun build(): DetektOptions = DetektOptions( parallel = parallel, configFileNames = configFiles, includes = includes, excludes = excludes, buildUponDefaultConfig = buildUponDefaultConfig ) } 
Enter fullscreen mode Exit fullscreen mode

4 - Em seguida, crie um novo arquivo detekt.kt em build-logic/src/.../decorations e declare uma função applyDetekt()

Essa configurações impoe que:

  1. Esse plugin só poderá ser chamado no build.gradle.kts da raíz
  2. Exista um arquivo .detekt.yml na raíz do projeto
  3. Exista um arquivo .detekt-compose.yml na raíz do projeto
import com.rsicarelli.kplatform.options.DetektOptions import io.gitlab.arturbosch.detekt.Detekt import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.gradle.api.Project import org.gradle.api.artifacts.MinimalExternalModuleDependency import org.gradle.kotlin.dsl.DependencyHandlerScope import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.withType internal fun Project.applyDetekt( detektOptions: DetektOptions ) { check(rootProject == this) { "Must be called on a root project" } pluginManager.apply("io.gitlab.arturbosch.detekt") extensions.configure<DetektExtension> { parallel = detektOptions.parallel toolVersion = libs.version("detekt") buildUponDefaultConfig = detektOptions.buildUponDefaultConfig config.setFrom(detektOptions.configFileNames.map { "$rootDir/$it" }) } tasks.withType<Detekt> { setSource(files(projectDir)) include(detektOptions.includes) exclude(detektOptions.excludes) } addDetektPlugins(listOf("compose", "formatting", "libraries")) } fun Project.addDetektPlugins(detektPlugins: List<String>) { fun DependencyHandlerScope.detektPlugin(dependency: MinimalExternalModuleDependency) { add("detektPlugins", dependency) } dependencies { detektPlugins.forEach { plugin -> detektPlugin(libs.findLibrary("detektRules-$plugin").get().get()) } } } 
Enter fullscreen mode Exit fullscreen mode

5 - Em seguida. vamos expor essa decoração no KPlatformPlugin.kt:

fun Project.detekt(builderAction: DetektBuilder = {}) = applyDetekt(DetektOptionsBuilder().apply(builderAction).build()) 
Enter fullscreen mode Exit fullscreen mode

6 - Sincronize o projeto. Em seguida, va até o build.gradle.kts da raiz do projeto, e inclua o plugin do detekt:

plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.arturbosch.detekt) apply false id(libs.plugins.rsicarelli.kplatform.get().pluginId) } 
Enter fullscreen mode Exit fullscreen mode

6 - Sincronize o projeto. Em seguida, aplique a decoração detekt() no mesmo arquivo:

import com.rsicarelli.kplatform.detekt plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.arturbosch.detekt) apply false id(libs.plugins.rsicarelli.kplatform.get().pluginId) } detekt() 
Enter fullscreen mode Exit fullscreen mode

7 - Vamos criar 2 arquivos na raíz do projeto: .detekt.yml e .detekt-compose.yml

8 - Sincronize o projeto. Perceba que uma série de tasks detektX foram adicionadas no projeto:

Image description

8 - Verifique que está funcionando rodando o seguinte comando.

Alternativamente, pode simplesmente dar um clique duplo na task detekt na lista de tasks do Gradle:

./gradlew detekt 
Enter fullscreen mode Exit fullscreen mode

Você vai perceber que vamos ter várias penalidades.

A seguir, vamos usar o Spotless para nos ajudar a reduzir a lista de faltas.

Adicionando o Spotless

O spotless é outra ferramenta indispensável nos projetos Kotlin.

Seu objetivo é formatar seu código magicamente de acordo com um estilo de código/configurações pre estabelecidas.

Novamente, não vamos focar muito nos detalhes da biblioteca, vamos direto ao uso

Passo a passo

1 - Declare as coordenadas do spotless no libs.versions.toml

[versions] spotless = "6.21.0" [libraries] gradlePlugin-spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } [plugins] diffplug-spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } 
Enter fullscreen mode Exit fullscreen mode

2 - Sincronize o projeto. Em seguida, vamos criar os arquivos SpotlessOptions na pasta build-logic/src/.../options:

Aqui, nossa plataforma será capaz de:

  1. Fornecer 2 configurações padrões para o projeto: SpotlessKtsRule e SpotlessXmlRule. Isso configura o spotless para nossos arquivos Gradle com extensão .kts, além de .xml do Android.
  2. Possibilta outras configurações de arquivos, de acordo com a necessidade de cada projeto.
data class SpotlessOptions( val fileRules: List<SpotlessFileRule> = listOf(SpotlessKtRule, SpotlessXmlRule), ) interface SpotlessFileRule { val fileExtension: String val targets: List<String> val excludes: List<String> } object SpotlessKtsRule : SpotlessFileRule { override val fileExtension: String = "kts" override val targets: List<String> = listOf("**/*.kts") override val excludes: List<String> = listOf("**/build/**/*.kts") } object SpotlessXmlRule : SpotlessFileRule { override val fileExtension: String = "xml" override val targets: List<String> = listOf("**/*.xml") override val excludes: List<String> = listOf("**/build/**/*.xml") } class SpotlessOptionsBuilder { var fileRules: List<SpotlessFileRule> = listOf(SpotlessKtRule, SpotlessXmlRule) internal fun build(): SpotlessOptions = SpotlessOptions( fileRules = fileRules ) } 
Enter fullscreen mode Exit fullscreen mode

3 - Vamos criar um arquivo spotless.kt dentro de build-logic/src/.../decorations e declarar a função applySpotless()

Note que:

  1. Estamos aplicando o Spotless no projeto raíz. Isso faz com que as formatações também aconteçam nos scripts da raíz, assim como na plataforma build-logic
  2. Estamos aplicando o Spotless também para todos os sub projetos.
  3. Estamos utilizando o klint como regras do Spotless
  4. O plugin assume que existe um arquivo .editorconfig na raíz do projeto
import com.diffplug.gradle.spotless.SpotlessExtension import com.diffplug.gradle.spotless.SpotlessPlugin import com.rsicarelli.kplatform.options.SpotlessOptions import org.gradle.api.Project import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure internal fun Project.applySpotless(spotlessConfig: SpotlessOptions) { val project = this configureSpotlessPlugin(spotlessConfig, project) rootProject.subprojects { configureSpotlessPlugin(spotlessConfig, project) } } private fun Project.configureSpotlessPlugin( spotlessConfig: SpotlessOptions, project: Project ) { apply<SpotlessPlugin>() extensions.configure<SpotlessExtension> { kotlin { target("src/**/*.kt") ktlint().setEditorConfigPath("${project.rootDir}/.editorconfig") } spotlessConfig.fileRules.forEach { spotlessFileRule -> with(spotlessFileRule) { format(fileExtension) { target(targets) targetExclude(excludes) } } } } } 
Enter fullscreen mode Exit fullscreen mode

4 - Crie um arquivo .editorconfig na raiz do projeto:

5 - Vamos expor essa decoração no KPlatformPlugin.kt:

fun Project.spotless(builderAction: SpotlessBuilder = { }) = applySpotless(SpotlessOptionsBuilder().apply(builderAction).build()) 
Enter fullscreen mode Exit fullscreen mode

6 - Sincronize o projeto. Em seguida, navegue até build.gradle.kts da raiz do projeto e declare o plugin do spotless:

plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.arturbosch.detekt) apply false alias(libs.plugins.diffplug.spotless) apply false id(libs.plugins.rsicarelli.kplatform.get().pluginId) } 
Enter fullscreen mode Exit fullscreen mode

7 - Sincronize o projeto. Em seguida. altere o mesmo build.gradle.kts e aplique a decoração spotless():

import com.rsicarelli.kplatform.detekt import com.rsicarelli.kplatform.spotless plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.arturbosch.detekt) apply false alias(libs.plugins.diffplug.spotless) apply false id(libs.plugins.rsicarelli.kplatform.get().pluginId) } detekt() spotless() 
Enter fullscreen mode Exit fullscreen mode

8 - Sincronize o projeto. Perceba que agora várias tasks spotless estarão disponíveis na lista de tarefas do Gradle:

Image description

9 - Verifque o funcionamento rodando o comando, ou clique duplo na tarefa spotlessApply na lista de tarefas do Gradle:

./gradlew spotlessApply 
Enter fullscreen mode Exit fullscreen mode

Sucesso!

O Spotless vai conseguir solucionar várias faltas automaticamente pra gente. Porém, tem algumas, como por exemplo nomeação dos arquivos, que não é suportado pelo Spotless.

Aproveitando, nessa branch, adicionei várias documentações para todas nossas API's da plataforma!

No próximo artigo, iremos fechar o essa série de posts, e contar um pouquinho sobre os próximos passos!

Top comments (0)