Do you follow these Kotlin Best Practices?

Beautify your code by improving your code style

Nishant Aanjaney Jalan
CodeX
4 min readNov 17, 2022

--

Any fool can write code that a computer can understand. Good programmers write code that humans can understand. — Martin Fowler

Photo by Pakata Goh on Unsplash

Avoid for-loops

For loop is a great construct for imperative programming. However, if there is a function that does the job for you, it is a better practice to use that function instead.

repeat

// DON'T
fun main() {
for (i in 0 until 10) {
println(i)
}
}

// DO
fun main() {
repeat(10) {
println(it)
}
}

forEach

// DON'T
fun main() {
val list = listOf(1, 2, 3, 4, 5, 6)
for (e in list) {
println(e)
}
}

// DO
fun main() {
listOf(1, 2, 3, 4, 5, 6).forEach {
println(it)
}
}

map

// DON'T
fun main() {
val list = listOf(1, 2, 3, 4, 5, 6)
val newList = mutableListOf<Int()
for (e in list) {
newList.add(e * e)
}
}

// DO
fun main() {
val list = listOf(1, 2, 3, 4, 5, 6)
val newList = list.map { it * it }
}

… and many more functions can be used to remove the need for for-loops.

Scope functions

There are 5main scope functions in Kotlin to take advantage of: let, also, apply, with and run.

let scope function

The let function is used to bypass possible null types.

fun main() {
val age = readLine()?.toIntOrNull()

age?.let {
println("You are $it years old");
} ?: println("Wrong input!");
}

apply scope function

This function is used when you wish to change the object's properties or behaviour.

fun main() {
val me = Person().apply {
name = "Nishant"
age = 19
gender = 'M'
}
println(me)
}

with scope function

This function is used when you want to use the properties of an object. It is just syntactic sugar for the apply function.

fun main() {
val me = with(Person()) {
name = "Nishant"
age = 19
gender = 'M'
}
println(me)
}

run scope function

This function is similar to the let function, but here the object reference is passed down as this instead of it.

fun main() {
val me = Person(
name = "Nishant",
age = 19,
gender = 'M'
)
me.run {
println("My name is $name and I am $age years old.");
}
}

also scope function

This takes in an object and performs some additional tasks on it.

fun main() {
Person(
name = "Nishant",
age = 19,
gender = 'M'
).also { println(it) }
}

Function references

The code used above isn’t the most optimal. We can further shorten our code by passing in function references to higher-order functions.

fun main() {
val input = readLine()
input?.let {
val sentence = it.split(" ")
sentence.map(String::length).also(::println)
}
}

String::length passes a lambda function of the type (String) -> Int and ::println passes a lambda function of the type (Int) -> Unit.

Extension values

If you are bound to use the same value in multiple places in your code, you might consider using extension values.

// Extension value that returns an int: 
// the index of the first single slash
private val String.hostEndIndex: Int
get() = indexOf('/' , indexOf("//") + 2)

fun main() {
val url = "https://medium.com/@cybercoder.naj"
val host = url.substring(0, url.hostEndIndex).substringAfter("//")
val path = url.substring(url.hostEndIndex).substringBefore("?")
}

Lift the return out of conditional structures

If you have a function that conditionally returns different values, instead of having the return inside every line of the conditional structure, you can pull out the return.

fun main() {
println(fibo(6))
}

fun fibo(n: Int): Int {
return when(n) {
0 -> 0
1 -> 1
else -> fibo(n - 1) + fibo(n - 2)
}
}

Furthermore, we can improve this by converting the function block to a single-line expression function.

fun main() {
println(fibo(6))
}

fun fibo(n: Int): Int = when (n) {
0 -> 0
1 -> 1
else -> fibo(n - 1) + fibo(n - 2)
}

Extension, operators, and infix functions

Extension operators

You can find the list of operator functions in the list here. These functions act as syntactic sugar in your code.

fun main() {
val list = mutableListOf(1, 2, 3)
(list + 4).forEach(::println)
}

operator fun <T> MutableList<T>.plus(t: T): MutableList<T> {
val newList = mutableListOf<T>().apply { addAll(this@plus) }
newList.add(t)
return newList
}

Infix

My recent article about Kotlin explains how we can compose two or more functions together.

infix fun <A, B, C> ((A) -> B).then(other: (B) -> C): (A) -> C {
return { other(this(it)) }
}

With this function, you can define a function such as:

val compose = ::f1 then ::f2
// f1 and f2 are lambda functions such that the return type
// of f1 is the same as the input type of f2.

Understand more about Function composition in my article

Typealias

When you have long type names, you can replace them with a synonymous name with the help of typealias.

typealias HttpHandler = (Request) -> Response
typealias QueryParams = List<Pair<String, String>>

fun configure(routes: List<Pair<String, HttpHandler>>) {...}

fun getParams(url: String): QueryParams {...}

Like my article? Check out more in my “Everything Kotlin” reading list.

Everything Kotlin

22 stories
Want to connect?

My GitHub profile.
My Portfolio website.

--

--

Nishant Aanjaney Jalan
CodeX

SWE Intern@JetBrains | UG Student | CS Tutor | Android & Web Developer | OCJP 8 | https://cybercoder-naj.github.io