Emanuel Moecklin
3 min readMay 4, 2021

--

In every language a "dedicated" engineer can write code that is hard to read and harder to maintain, Kotlin is no exception. It seems unfair to use badly written code to make the point that Kotlin has a consistency problem (not sure what that means really) and also a readability and safety problem but let's go through the examples.

The logger example

If you're trying to replace some of the Java code by Kotlin code, don't start with the low-level classes but the high-level modules. While it's possible (and sometimes necessary) to use Kotlin code from Java code, using Java from Kotlin code is certainly more seamless and requires less patches and workarounds (usually none at all).

The logger specifically is certainly one of these low-level components that doesn't need a Kotlin replacement and I would also say that every more than basic application uses a dependency injection framework so all these static objects/methods are obsolete. I e.g. use Koin and having access to the logger is a one-liner:

val logger: Logger by inject()

(oh well, the class also needs to implement KoinComponent, so maybe a 1.2 liner)

The readability example

I absolutely agree that scopes and functions (especially extension functions and functions with lamdas and receivers -> DSL) can be used in horrific ways and your example is certainly a bad one (or a good one to make your point).

The question is how much complexity are you willing to accept in a programming language and how mature is an engineering organization in terms of process to handle the complexity of the language (keep the seniors in check to not over-engineer their code, keep the juniors in check to write reusable and maintainable code etc.)? A "Java organization" might answer the question when it comes to Kotlin with: not acceptable, an organization transitioning from node.js / JavaScript to a Kotlin back-end might find that the more rigid Kotlin is the correct next step. I've seen my share of terrible Java code that matches your example e.g. using generics, RxJava or annotation hell in Spring. While I do agree that Kotlin offers more "temptations", it comes down to that one question, can the organization handle it?

On a side note, I'd like to mention that Kotlin is still a new language and there's a learning process that starts with simple code moving to complex to over-engineered code (as people learn and apply more features of the language) and then hopefully back to simpler code as people realize that maintainability is key.

The maintainability example

I don't get your point here. How is Kotlin worse than Java in terms of "decoupling, consistency and reuse of the code" (the answer is: it's not)? Your problem seems to be that "non-back-end or inexperienced developers join the team" which obviously leads to code quality issues but that has nothing to do with the language itself, it's a purely organizational problem.

I think Kotlin hits the sweet spot between Java and Scala. If you don't use extension functions and functions with lambdas and receivers excessively and make sure that functions are used in a fluent instead of a nested way then the benefits of the language are huge (compared to Java). The result can be less code that is easier to read and safer on top of it (e.g. thread safety). To support that last arguments some code snippets that I think show the strength of the language:

exit a function if the image can’t be retrieved or doesn’t exist:

val imageFile = getImage() ?: return

create a database connection pool and configure it:

val config = HikariConfig().apply {
jdbcUrl = url
username = user
password = pw
addDataSourceProperty("cachePrepStmts", "true")
}

open a file and process it (success and failure case)

when (val file = openFile(name)) {
null -> handleFileError(name)
else -> processFile(file)
}

alternatively:

openFile(name)
?.run { processFile(this) }
?: handleFileError(name)

acquire a lock which will be released automatically because of use and the fact that Lock implements the Closable interface:

Lock(logger)
.acquire(provider)
?.use {
// do stuff with the locked resources
}

Parse a string into an Instant and handle success and failure case (using kotlin-result):

runCatching { Instant.parse(reader.nextString()) }
.onSuccess { processInstance(it) }
.onFailure { throw ValidationException(it.message) }

--

--