What are the functions of the Kotlin extension and where to apply them correctly?

What are the functions of the Kotlin extension and where to apply them correctly?

My name is Vyacheslav Aksenov, I am a professional Java/Kotlin backend developer in a large enterprise. My area of responsibility is designing systems from a set of services and building complex business logic. Also from time to time I write my pet projects, some of which you can find in my github:

I have been using Kotlin mainly for writing code for more than a year now, and in this article I want to focus on one of the constructs of this language – extension functions. And also consider cases when using them can help, and when it will only make your life more difficult.

What are the functions of the Kotlin extension really

According to its idea, the Kotlin extension function is an additional method for any object, even for a potentially non-existent (nullable) one. This tool is a direct implementation of redefining the methods of the decorator design pattern. Thanks to the convenient syntax, it is very easy to use this sugar add-in in Kotlin.

Consider an example:

data class Book(
    val id: Long,
    val author: String,
    val title: String,
    val price: BigDecimal
)

class SomeService {

  fun analyzeBook(book: Book) {
    val formattedInfo = book.getFormattedInfo()
    ....
  }

  private fun Book.getFormattedInfo(): String = "Book $author - $title has price - $price"
}

The usual dto class Book, which has a certain set of fields. And there is a SomeService class, which has a public method for analyzing the book. We assume that formatted data from the dto is required to analyze the book. However, the class is closed for expansion, perhaps it lies in a third-party library. Using the extension function, you can write a method that seems to belong to this class. At the same time, note that any access modifier can be hung on this method — in this case, private.

If you compile this code and parse the bytecode in Java, then the construction with the method will look like this:

private static final String getFormattedAmount(Book $this$getFormattedInfo) {
   return "Book " + $this$getFormattedAmount.getAuthor() + " - " + $this$getFormattedAmount.getTitle() + " has price - " + $this$getFormattedAmount.getPrice();
}

Thus, we have seen from our own experience that extension functions for JVM are static final methods. But each tool has a range of tasks for which it is best suited. For example, hammering nails with a sandwich will be very uncomfortable, you need to eat sandwiches. Let’s determine in which tasks the extension functions perform best and really make life easier.

Extending the API of a class that doesn’t belong to you

For example, the class of someone else’s library has an extremely unsuccessful structure, and you constantly need to get into the depth of this structure in the code. Writing your own model will be too expensive a solution to the problem, but the extension functions will be very useful:

// чужой сложный класс
data class Bank(
    val bankInfo: BankInfo,
    ....
)

data class BankInfo(
    val bankAddressInfo: BankAddressInfo
    ....
)

data class BankAddressInfo(
    val city: String,
    .....
)
// конец сложного класса

// функция расширения
fun Bank.getCity() = bankInfo.bankAddressInfo.city

// сравнение
fun example(client: Client) {
    val address = client.personalInfo.address.city // без расширения
    val address = client.getCity() // с расширением
}

Converting models from one to another

Potentially dangerous use, but if you keep the following rules in mind, it is very convenient.

// файл с утилитами

fun OwnClass.asOtherOwnClass(): OtherOwnClass = // логика конвертации
fun OwnClass1.asOtherOwnClass1(): OtherOwnClass1 = // логика конвертации
fun OwnClass2.asOtherOwnClass2(): OtherOwnClass2 = // логика конвертации

Pay attention to the naming of functions. You should start them with unpopular names of methods, or make converters private in the scope of visibility only of those places where they are needed. Otherwise, you can litter the entire context of the project. Take a look at the exaggerated example:

Illustration: Kotlin extension functions

Extending the capabilities of any class, but in a limited scope

Imagine that there is a class for which you need to write a complex method that will be used only in one service. This can be done using the Kotlin extension function, the main thing is to make the extension private.

private fun OwnClass.asOwnClass2(): OwnClass2 {
    val metaField1 = thirdPartyClient.getMetaField1()
    val metaField2 = thirdPartyClient.getMetaField2()
    // ...
}

In this example, the service field — thirdPartyClient is used inside the Kotlin extension function, from which some meta data is obtained. This is a perfectly acceptable use of the extension function, but you should be extremely careful if you put any external field inside such a method! There must be a good reason for this!

Expanding the functionality of generics

Yes, Kotln allows you to do even this, and sometimes it’s really useful. For example, you may need to extend the API of all heirs of a certain class, but without adding a method to this class.

fun <T> T.someMethod() = {
  // некая логика
}

Be careful, if the name of this method is too general, you risk clogging up the IDE context very much in the places where this method is used.

Why you should never use extension functions

Remember, if you use the extension functions for the following purposes, then either you or future developers in your team will sooner or later stumble over this code and suffer.

  • First. Uncontrolled expansion of any functionality instead of writing a regular method at the global level will very much clog up the IDE context and become a big pain in finding all the places where some class has been expanded.
  • Second. If the size of the Kotlin extension function exceeds 5 lines, then it’s time to write a regular method.
fun OwnClass.someMethod(someParam: Boolean):  {
    val metaField1 = thirdPartyClient.getMetaField1()
    val metaField2 = thirdPartyClient.getMetaField2()
    val metaField2 = thirdPartyClient.getMetaField2()
    if (someParam) {
        val metaField = ....
        // ...
    }
    // ...
}

Otherwise, you risk sooner or later facing the need to factor such a method, simply because it will be very difficult to maintain it without violating common sense.

And the last. Never change global parameters using extension functions. Otherwise, you will not be able to adequately track at what exact moment you did it.

In conclusion.

Extension functions are Kotlin’s convenient syntactic sugar, which allows you to use the language almost any way. However, be vigilant and apply them only where they will really simplify life. These methods should be small and solve a specific task, no one intends to do cmd+click on the method and fall into hell with a huge amount of business logic.

Use them as an auxiliary tool and do not abuse their power 🙂

Outsourcing Software Development Services | Dedicated Solutions Outsourcing

Ready to see us in action:

More To Explore

IWanta.tech
Logo
Enable registration in settings - general
Have any project in mind?

Contact us:

small_c_popup.png