Kotlin Code Organization

栏目: IT技术 · 发布时间: 4年前

内容简介:What’s the motivation behind organizing the code? Two points come to mind.Sounds empathic. Where do we start?Maven introduced a concept of

What’s the motivation behind organizing the code? Two points come to mind.

  • Help humans. Consistent environments are easier to understand and adapt. Storing the source code in src/ instead of _k_/ makes it easier to find.
  • Help machines. Build systems need hints. The code in main/ should be assembled all the time, while test/ is test-specific and shouldn’t make it to a production environment.

Sounds empathic. Where do we start?

SDL

Maven introduced a concept of the Standard Directory Layout . Gradle tends to follow it bringing so-called source sets along the way. The following file system tree is SDL-compliant.

.
├── main
│   ├── java
│   │   └── Code.java
│   ├── kotlin
│   │   └── Kode.kt
│   └── resources
│       └── production.xml
└── test
    ├── java
    │   └── CodeTests.java
    ├── kotlin
    │   └── KodeTests.kt
    └── resources
        └── test.xml
  • main , test — source sets. Include everything related to the code scope — like the production application ( main ), unit tests ( test ) and more ( androidTest and friends).
  • java , kotlin , resources — source set implementation details. Unit tests can be written in Kotlin and Groovy at the same time, the production code might be a Java + Scala mix.

This two-level structure allows us to organize the code based on a functional target (production, tests) and on implementation details (language, tools). Let’s leverage this.

Tips

src/{sourceSet}/kotlin

Storing Kotlin files in the Kotlin-specific directory sounds obvious but a lot of projects are 100% Kotlin and store the source code as Java. Take a look at LeakCanary , Muzei , OkHttp , Scarlet , Timber , ViewPump , Workflow and more. I see a number of reasons behind this.

  • The Kotlin compiler supports mixing Java and Kotlin code, so there is no punishment from the tooling.
  • Projects migrate from Java to Kotlin using the mixing and forget to change the source set configuration.
  • The Gradle Android plugin requires additional configuration which might be not trivial.

To be honest, there is nothing outright wrong with mixing Java and Kotlin code. It’s more accurate and expectable to store them separately. Also, it might help with Java → Kotlin migration efforts — it’s easier to observe that the Java directory is shrinking and the Kotlin one is growing than running cloc all the time.

src/{sourceSet}/kotlinX

There is a common issue of organizing Kotlin extensions. I’ve seen a lot of projects with the Extensions.kt garbage fire. When everything is in a single file — it’s easier to overlook an extension and write a new one placed at… extensions/Extensions.kt . Guess what happens next.

I suggest storing extensions using the target class package and file names. Plus — move them to the kotlinX/ directory as a separation of the project code from additions to the external one. This approach leads to a better separation of concerns.

For example, the following io.reactivex.functions.Consumer extension should be placed at src/main/kotlinX/io/reactivex/functions/Consumer.kt .

package io.reactivex.functions

fun Consumer<Unit>.asAction() = Action { accept(Unit) }

Bonus — imports start to make sense.

- import hello.there.asAction
+ import io.reactivex.functions.asAction

src/testFixtures/kotlin

A growing test / specification suite might be not pleasant to look at.Using fakes is great but there is a possibility of having a huge file tree with mixed tests and fakes.

.
└── src
    └── test
        └── kotlin
            ├── ApplicationSpec.kt
            ├── FakeApplication.kt
            ├── FakePermissions.kt
            └── PermissionsSpec.kt

Since fakes and tests are different things — I suggest to split them in the digital world as well.

.
└── src
    ├── test
    │   └── kotlin
    │       ├── ApplicationSpec.kt
    │       └── PermissionsSpec.kt
    └── testFixtures
        └── kotlin
            ├── FakeApplication.kt
            └── FakePermissions.kt

In fact, Gradle supports this approach for the Java code and with benefits — it’s possible to share testFixtures across modules. However, it doesn’t work with Gradle Kotlin and Android plugins.

Gradle API

The code below will use the Gradle Kotlin DSL but it can be adapted to the Groovy DSL as well. The code was run against Gradle 6.1.1, Gradle Kotlin plugin 1.3.61 and Gradle Android plugin 3.5.3.

JVM

Gradle uses a couple of classes as an API to configure the source code location:

  • SourceDirectorySet — a set of source code files;
  • SourceSet — a group of SourceDirectorySet s for Java code and resources.

The Gradle Kotlin for JVM plugin adds another one.

  • KotlinSourceSet — like SourceSet , but for Kotlin sources. Bonus — it configures src/main/kotlin and src/test/kotlin automatically.

The DSL works with those classes.

  • Single module (put in the module build.gradle.kts file).

    import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
    
    // Get a SourceSet collection
    sourceSets {
        // Get a SourceSet by name
        named("source set name") {
            // Resolve a KotlinSourceSet
            withConvention(KotlinSourceSet::class) {
                // Configure Kotlin SourceDirectorySet
                kotlin.srcDirs("path A", "path B", "path C")
            }
        }
    }
  • Multiple modules (put in the root build.gradle.kts file).

    import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
    
    subprojects {
        // The sourceSets function is not available at root so we use a different syntax
        configure<SourceSetContainer> {
            named("source set name") {
                withConvention(KotlinSourceSet::class) {
                    kotlin.srcDirs("path A", "path B", "path C")
                }
            }
        }
    }

Android

Gradle Android plugin ignores native Gradle source set infrastructure and introduces its own. To be fair, the Android API tries to mimic the Gradle one, so I suspect the reinvention was done for a reason.

  • AndroidSourceDirectorySet (mimics Gradle SourceDirectorySet ) — a set of source code files;
  • AndroidSourceSet (mimics Gradle SourceSet ) — a group of AndroidSourceDirectorySet s for Java code and resources, Android resources, assets, AIDL, RenderScript files and more.

The Gradle Kotlin for Android plugin doesn’t provide a KotlinAndroidSourceSet (like KotlinSourceSet for JVM). Fortunately enough we can use the Java AndroidSourceSet instead (thanks to mixing).

The DSL is similar to the JVM one.

  • Single module (put in the module build.gradle.kts file).

    android {
        // Get an AndroidSourceSet collection
        sourceSets {
            // Get an AndroidSourceSet by name
            named("source set name") {
                // Configure Java AndroidSourceDirectorySet
                java.srcDirs("path A", "path B", "path C")
            }
        }
    }
  • Multiple modules (put in the root build.gradle.kts file).

    import com.android.build.gradle.AppPlugin
    import com.android.build.gradle.BaseExtension
    import com.android.build.gradle.LibraryPlugin
    
    subprojects {
        // Since the API comes from a plugin we have to wait for it
        plugins.matching { it is AppPlugin || it is LibraryPlugin }.whenPluginAdded {
            // The android function is not available at root so we use a different syntax
            configure<BaseExtension> {
                sourceSets {
                    named("source set name") {
                        java.srcDirs("path A", "path B", "path C")
                    }
                }
            }
        }
    }

Gradle Implementation

Nice, we can use the Gradle API to apply our tips! Snippets below are DSL declarations that can be used in both single and multiple module configurations described above.

JVM

named("main") {
    withConvention(KotlinSourceSet::class) {
        // Gradle Kotlin for JVM plugin configures "src/main/kotlin" on its own
        kotlin.srcDirs("src/main/kotlinX")
    }
}

named("test") {
    withConvention(KotlinSourceSet::class) {
        // Gradle Kotlin for JVM plugin configures "src/test/kotlin" on its own
        kotlin.srcDirs("src/test/kotlinX", "src/testFixtures/kotlin")
    }
}

Android

named("main") {
    java.srcDirs("src/main/kotlin", "src/main/kotlinX")
}

named("test") {
    java.srcDirs("src/test/kotlin", "src/test/kotlinX", "src/testFixtures/kotlin")
}

Next?

Don’t afraid to configure source sets — think about what can be done better and adapt. The Gradle API might be not intuitive at first glance — especially when Kotlin and Android are brought in the mix — but almost everything can be achieved.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C++语言的设计和演化

C++语言的设计和演化

[美] Bjarne Stroustrup / 裘宗燕 / 机械工业出版社 / 2002-1 / 48.00元

这本书是C++的设计者关于C++语言的最主要著作之一。作者综合性地论述了C++的历史和发展,C++中各种重要机制的本质意义和设计背景,这些机制的基本用途和使用方法,讨论了C++所适合的应用领域及其未来的发展前景。一起来看看 《C++语言的设计和演化》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

SHA 加密
SHA 加密

SHA 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具