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.


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

查看所有标签

猜你喜欢:

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

PHP5与MySQL5 Web开发技术详解

PHP5与MySQL5 Web开发技术详解

杜江 / 电子工业出版社 / 2007-11 / 79.00元

《PHP5与MySQL5 Web开发技术详解》(含光盘)是目前中文版本第一个真正介绍PHP5及MYSQL5新增语法与功能的权威宝典!一起来看看 《PHP5与MySQL5 Web开发技术详解》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

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

各进制数互转换器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具